/*
 * Copyright 2011-2014 Andrew Smith
 * Copyright 2011-2014 Con Kolivas
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option)
 * any later version.  See COPYING for more details.
 */
#define _MEMORY_DEBUG_MASTER 1

#include "config.h"

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>

#include "compat.h"
#include "miner.h"
#include "util.h"
#include "klist.h"

#if defined(USE_BFLSC) || defined(USE_AVALON) || defined(USE_AVALON2) || defined(USE_AVALON4) || \
  defined(USE_HASHFAST) || defined(USE_BITFURY) || defined(USE_BLOCKERUPTER) || defined(USE_KLONDIKE) || \
    defined(USE_KNC) || defined(USE_BAB) || defined(USE_DRILLBIT) || \
    defined(USE_MINION) || defined(USE_COINTERRA) || defined(USE_BITMINE_A1) || \
    defined(USE_BMSC) || defined(USE_BITMAIN) || defined(USE_SP10) || defined(USE_SP30) || \
    defined(USE_ICARUS) || defined(USE_HASHRATIO)
#define HAVE_AN_ASIC 1
#endif

#if defined(USE_BITFORCE) || defined(USE_MODMINER)
#define HAVE_AN_FPGA 1
#endif

// BUFSIZ varies on Windows and Linux
#define TMPBUFSIZ   8192

// Number of requests to queue - normally would be small
// However lots of PGA's may mean more
#define QUEUE   100

#if defined WIN32
static char WSAbuf[1024];

struct WSAERRORS
{
    int id;
    char *code;
} WSAErrors[] =
{
    { 0,            "No error" },
    { WSAEINTR,     "Interrupted system call" },
    { WSAEBADF,     "Bad file number" },
    { WSAEACCES,        "Permission denied" },
    { WSAEFAULT,        "Bad address" },
    { WSAEINVAL,        "Invalid argument" },
    { WSAEMFILE,        "Too many open sockets" },
    { WSAEWOULDBLOCK,   "Operation would block" },
    { WSAEINPROGRESS,   "Operation now in progress" },
    { WSAEALREADY,      "Operation already in progress" },
    { WSAENOTSOCK,      "Socket operation on non-socket" },
    { WSAEDESTADDRREQ,  "Destination address required" },
    { WSAEMSGSIZE,      "Message too long" },
    { WSAEPROTOTYPE,    "Protocol wrong type for socket" },
    { WSAENOPROTOOPT,   "Bad protocol option" },
    { WSAEPROTONOSUPPORT,   "Protocol not supported" },
    { WSAESOCKTNOSUPPORT,   "Socket type not supported" },
    { WSAEOPNOTSUPP,    "Operation not supported on socket" },
    { WSAEPFNOSUPPORT,  "Protocol family not supported" },
    { WSAEAFNOSUPPORT,  "Address family not supported" },
    { WSAEADDRINUSE,    "Address already in use" },
    { WSAEADDRNOTAVAIL, "Can't assign requested address" },
    { WSAENETDOWN,      "Network is down" },
    { WSAENETUNREACH,   "Network is unreachable" },
    { WSAENETRESET,     "Net connection reset" },
    { WSAECONNABORTED,  "Software caused connection abort" },
    { WSAECONNRESET,    "Connection reset by peer" },
    { WSAENOBUFS,       "No buffer space available" },
    { WSAEISCONN,       "Socket is already connected" },
    { WSAENOTCONN,      "Socket is not connected" },
    { WSAESHUTDOWN,     "Can't send after socket shutdown" },
    { WSAETOOMANYREFS,  "Too many references, can't splice" },
    { WSAETIMEDOUT,     "Connection timed out" },
    { WSAECONNREFUSED,  "Connection refused" },
    { WSAELOOP,     "Too many levels of symbolic links" },
    { WSAENAMETOOLONG,  "File name too long" },
    { WSAEHOSTDOWN,     "Host is down" },
    { WSAEHOSTUNREACH,  "No route to host" },
    { WSAENOTEMPTY,     "Directory not empty" },
    { WSAEPROCLIM,      "Too many processes" },
    { WSAEUSERS,        "Too many users" },
    { WSAEDQUOT,        "Disc quota exceeded" },
    { WSAESTALE,        "Stale NFS file handle" },
    { WSAEREMOTE,       "Too many levels of remote in path" },
    { WSASYSNOTREADY,   "Network system is unavailable" },
    { WSAVERNOTSUPPORTED,   "Winsock version out of range" },
    { WSANOTINITIALISED,    "WSAStartup not yet called" },
    { WSAEDISCON,       "Graceful shutdown in progress" },
    { WSAHOST_NOT_FOUND,    "Host not found" },
    { WSANO_DATA,       "No host data of that type was found" },
    { -1,           "Unknown error code" }
};

char *WSAErrorMsg(void)
{
    int i;
    int id = WSAGetLastError();

    /* Assume none of them are actually -1 */
    for (i = 0; WSAErrors[i].id != -1; i++)
        if (WSAErrors[i].id == id)
            break;

    sprintf(WSAbuf, "Socket Error: (%d) %s", id, WSAErrors[i].code);

    return &(WSAbuf[0]);
}
#endif

#if defined(__APPLE__)
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
#endif

static const char *UNAVAILABLE = " - API will not be available";
static const char *MUNAVAILABLE = " - API multicast listener will not be available";

static const char *BLANK = "";
static const char *COMMA = ",";
#define COMSTR ","
static const char SEPARATOR = '|';
#define SEPSTR "|"
#define CMDJOIN '+'
#define JOIN_CMD "CMD="
#define BETWEEN_JOIN SEPSTR

static const char *APIVERSION = "3.1";
static const char *DEAD = "Dead";
static const char *SICK = "Sick";
static const char *NOSTART = "NoStart";
static const char *INIT = "Initialising";
static const char *DISABLED = "Disabled";
static const char *ALIVE = "Alive";
static const char *REJECTING = "Rejecting";
static const char *UNKNOWN = "Unknown";

static __maybe_unused const char *NONE = "None";

static const char *YES = "Y";
static const char *NO = "N";
static const char *NULLSTR = "(null)";

static const char *TRUESTR = "true";
static const char *FALSESTR = "false";

static const char *SHA256STR = "sha256";

static const char *DEVICECODE = ""
#ifdef USE_BMSC
                                "BTM "
#endif
#ifdef USE_BITMAIN
                                "BTM "
#endif
#ifdef USE_AVALON
                                "AVA "
#endif
#ifdef USE_BAB
                                "BaB "
#endif
#ifdef USE_BFLSC
                                "BAS "
#endif
#ifdef USE_BITFORCE
                                "BFL "
#endif
#ifdef USE_BITFURY
                                "BFU "
#endif
#ifdef USE_BLOCKERUPTER
                                "BET "
#endif
#ifdef USE_DRILLBIT
                                "DRB "
#endif
#ifdef USE_HASHFAST
                                "HFA "
#endif
#ifdef USE_HASHRATIO
                                "HRO "
#endif
#ifdef USE_BITMINE_A1
                                "BA1 "
#endif
#ifdef USE_ICARUS
                                "ICA "
#endif
#ifdef USE_KNC
                                "KnC "
#endif
#ifdef USE_MINION
                                "MBA "
#endif
#ifdef USE_MODMINER
                                "MMQ "
#endif
#ifdef USE_COINTERRA
                                "CTA "
#endif
#ifdef USE_SP10
                                "SPN "
#endif
#ifdef USE_SP30
                                "S30 "
#endif


                                "";

static const char *OSINFO =
#if defined(__linux)
    "Linux";
#else
#if defined(__APPLE__)
    "Apple";
#else
#if defined (WIN32)
    "Windows";
#else
#if defined(unix)
    "Unix";
#else
    "Unknown";
#endif
#endif
#endif
#endif

#define _DEVS       "DEVS"
#define _POOLS      "POOLS"
#define _SUMMARY    "SUMMARY"
#define _NONCENUM   "NONCENUM"
#define _STATUS     "STATUS"
#define _VERSION    "VERSION"
#define _MINECONFIG "CONFIG"

#ifdef HAVE_AN_FPGA
#define _PGA        "PGA"
#endif

#ifdef HAVE_AN_ASIC
#define _ASC        "ASC"
#endif

#define _PGAS       "PGAS"
#define _ASCS       "ASCS"
#define _NOTIFY     "NOTIFY"
#define _DEVDETAILS "DEVDETAILS"
#define _BYE        "BYE"
#define _RESTART    "RESTART"
#define _MINESTATS  "STATS"
#define _CHECK      "CHECK"
#define _MINECOIN   "COIN"
#define _DEBUGSET   "DEBUG"
#define _SETCONFIG  "SETCONFIG"
#define _USBSTATS   "USBSTATS"
#define _LCD        "LCD"

static const char ISJSON = '{';
#define JSON0       "{"
#define JSON1       "\""
#define JSON2       "\":["
#define JSON3       "]"
#define JSON4       ",\"id\":1"
// If anyone cares, id=0 for truncated output
#define JSON4_TRUNCATED ",\"id\":0"
#define JSON5       "}"
#define JSON6       "\":"

#define JSON_START  JSON0
#define JSON_DEVS   JSON1 _DEVS JSON2
#define JSON_POOLS  JSON1 _POOLS JSON2
#define JSON_SUMMARY    JSON1 _SUMMARY JSON2
#define JSON_NONCENUM   JSON1 _NONCENUM JSON2

#define JSON_STATUS JSON1 _STATUS JSON2
#define JSON_VERSION    JSON1 _VERSION JSON2
#define JSON_MINECONFIG JSON1 _MINECONFIG JSON2
#define JSON_ACTION JSON0 JSON1 _STATUS JSON6

#ifdef HAVE_AN_FPGA
#define JSON_PGA    JSON1 _PGA JSON2
#endif

#ifdef HAVE_AN_ASIC
#define JSON_ASC    JSON1 _ASC JSON2
#endif

#define JSON_PGAS   JSON1 _PGAS JSON2
#define JSON_ASCS   JSON1 _ASCS JSON2
#define JSON_NOTIFY JSON1 _NOTIFY JSON2
#define JSON_DEVDETAILS JSON1 _DEVDETAILS JSON2
#define JSON_BYE    JSON1 _BYE JSON1
#define JSON_RESTART    JSON1 _RESTART JSON1
#define JSON_CLOSE  JSON3
#define JSON_MINESTATS  JSON1 _MINESTATS JSON2
#define JSON_CHECK  JSON1 _CHECK JSON2
#define JSON_MINECOIN   JSON1 _MINECOIN JSON2
#define JSON_DEBUGSET   JSON1 _DEBUGSET JSON2
#define JSON_SETCONFIG  JSON1 _SETCONFIG JSON2
#define JSON_USBSTATS   JSON1 _USBSTATS JSON2
#define JSON_LCD    JSON1 _LCD JSON2
#define JSON_END    JSON4 JSON5
#define JSON_END_TRUNCATED  JSON4_TRUNCATED JSON5
#define JSON_BETWEEN_JOIN   ","

static const char *JSON_COMMAND = "command";
static const char *JSON_PARAMETER = "parameter";

#define MSG_POOL 7
#define MSG_NOPOOL 8
#define MSG_DEVS 9
#define MSG_NODEVS 10
#define MSG_SUMM 11
#define MSG_INVCMD 14
#define MSG_MISID 15
#define MSG_NONCE_NUM 16

#define MSG_VERSION 22
#define MSG_INVJSON 23
#define MSG_MISCMD 24
#define MSG_MISPID 25
#define MSG_INVPID 26
#define MSG_SWITCHP 27
#define MSG_MISVAL 28
#define MSG_NOADL 29
#define MSG_INVINT 31
#define MSG_MINECONFIG 33
#define MSG_MISFN 42
#define MSG_BADFN 43
#define MSG_SAVED 44
#define MSG_ACCDENY 45
#define MSG_ACCOK 46
#define MSG_ENAPOOL 47
#define MSG_DISPOOL 48
#define MSG_ALRENAP 49
#define MSG_ALRDISP 50
#define MSG_DISLASTP 51
#define MSG_MISPDP 52
#define MSG_INVPDP 53
#define MSG_TOOMANYP 54
#define MSG_ADDPOOL 55

#ifdef HAVE_AN_FPGA
#define MSG_PGANON 56
#define MSG_PGADEV 57
#define MSG_INVPGA 58
#endif

#define MSG_NUMPGA 59
#define MSG_NOTIFY 60

#ifdef HAVE_AN_FPGA
#define MSG_PGALRENA 61
#define MSG_PGALRDIS 62
#define MSG_PGAENA 63
#define MSG_PGADIS 64
#define MSG_PGAUNW 65
#endif

#define MSG_REMLASTP 66
#define MSG_ACTPOOL 67
#define MSG_REMPOOL 68
#define MSG_DEVDETAILS 69
#define MSG_MINESTATS 70
#define MSG_MISCHK 71
#define MSG_CHECK 72
#define MSG_POOLPRIO 73
#define MSG_DUPPID 74
#define MSG_MISBOOL 75
#define MSG_INVBOOL 76
#define MSG_FOO 77
#define MSG_MINECOIN 78
#define MSG_DEBUGSET 79
#define MSG_PGAIDENT 80
#define MSG_PGANOID 81
#define MSG_SETCONFIG 82
#define MSG_UNKCON 83
#define MSG_INVNUM 84
#define MSG_CONPAR 85
#define MSG_CONVAL 86
#define MSG_USBSTA 87
#define MSG_NOUSTA 88

#ifdef HAVE_AN_FPGA
#define MSG_MISPGAOPT 89
#define MSG_PGANOSET 90
#define MSG_PGAHELP 91
#define MSG_PGASETOK 92
#define MSG_PGASETERR 93
#endif

#define MSG_ZERMIS 94
#define MSG_ZERINV 95
#define MSG_ZERSUM 96
#define MSG_ZERNOSUM 97
#define MSG_PGAUSBNODEV 98
#define MSG_INVHPLG 99
#define MSG_HOTPLUG 100
#define MSG_DISHPLG 101
#define MSG_NOHPLG 102
#define MSG_MISHPLG 103

#define MSG_NUMASC 104
#ifdef HAVE_AN_ASIC
#define MSG_ASCNON 105
#define MSG_ASCDEV 106
#define MSG_INVASC 107
#define MSG_ASCLRENA 108
#define MSG_ASCLRDIS 109
#define MSG_ASCENA 110
#define MSG_ASCDIS 111
#define MSG_ASCUNW 112
#define MSG_ASCIDENT 113
#define MSG_ASCNOID 114
#endif
#define MSG_ASCUSBNODEV 115

#ifdef HAVE_AN_ASIC
#define MSG_MISASCOPT 116
#define MSG_ASCNOSET 117
#define MSG_ASCHELP 118
#define MSG_ASCSETOK 119
#define MSG_ASCSETERR 120
#endif

#define MSG_INVNEG 121
#define MSG_SETQUOTA 122
#define MSG_LOCKOK 123
#define MSG_LOCKDIS 124
#define MSG_LCD 125

enum code_severity
{
    SEVERITY_ERR,
    SEVERITY_WARN,
    SEVERITY_INFO,
    SEVERITY_SUCC,
    SEVERITY_FAIL
};

enum code_parameters
{
    PARAM_PGA,
    PARAM_ASC,
    PARAM_PID,
    PARAM_PGAMAX,
    PARAM_ASCMAX,
    PARAM_PMAX,
    PARAM_POOLMAX,

// Single generic case: have the code resolve it - see below
    PARAM_DMAX,

    PARAM_CMD,
    PARAM_POOL,
    PARAM_STR,
    PARAM_BOTH,
    PARAM_BOOL,
    PARAM_SET,
    PARAM_INT,
    PARAM_NONE
};

struct CODES
{
    const enum code_severity severity;
    const int code;
    const enum code_parameters params;
    const char *description;
} codes[] =
{
    { SEVERITY_SUCC,  MSG_POOL,    PARAM_PMAX, "%d Pool(s)" },
    { SEVERITY_ERR,   MSG_NOPOOL,  PARAM_NONE, "No pools" },

    {
        SEVERITY_SUCC,  MSG_DEVS,    PARAM_DMAX,
#ifdef HAVE_AN_ASIC
        "%d ASC(s)"
#endif
#if defined(HAVE_AN_ASIC) && defined(HAVE_AN_FPGA)
        " - "
#endif
#ifdef HAVE_AN_FPGA
        "%d PGA(s)"
#endif
    },

    {
        SEVERITY_ERR,   MSG_NODEVS,  PARAM_NONE, "No "
#ifdef HAVE_AN_ASIC
        "ASCs"
#endif
#if defined(HAVE_AN_ASIC) && defined(HAVE_AN_FPGA)
        "/"
#endif
#ifdef HAVE_AN_FPGA
        "PGAs"
#endif
    },

    { SEVERITY_SUCC,  MSG_SUMM,    PARAM_NONE, "Summary" },
    { SEVERITY_SUCC,  MSG_NONCE_NUM,PARAM_NONE,"Nonce num" },
    { SEVERITY_ERR,   MSG_INVCMD,  PARAM_NONE, "Invalid command" },
    { SEVERITY_ERR,   MSG_MISID,   PARAM_NONE, "Missing device id parameter" },
#ifdef HAVE_AN_FPGA
    { SEVERITY_ERR,   MSG_PGANON,  PARAM_NONE, "No PGAs" },
    { SEVERITY_SUCC,  MSG_PGADEV,  PARAM_PGA,  "PGA%d" },
    { SEVERITY_ERR,   MSG_INVPGA,  PARAM_PGAMAX,   "Invalid PGA id %d - range is 0 - %d" },
    { SEVERITY_INFO,  MSG_PGALRENA,PARAM_PGA,  "PGA %d already enabled" },
    { SEVERITY_INFO,  MSG_PGALRDIS,PARAM_PGA,  "PGA %d already disabled" },
    { SEVERITY_INFO,  MSG_PGAENA,  PARAM_PGA,  "PGA %d sent enable message" },
    { SEVERITY_INFO,  MSG_PGADIS,  PARAM_PGA,  "PGA %d set disable flag" },
    { SEVERITY_ERR,   MSG_PGAUNW,  PARAM_PGA,  "PGA %d is not flagged WELL, cannot enable" },
#endif
    { SEVERITY_SUCC,  MSG_NUMPGA,  PARAM_NONE, "PGA count" },
    { SEVERITY_SUCC,  MSG_NUMASC,  PARAM_NONE, "ASC count" },
    { SEVERITY_SUCC,  MSG_VERSION, PARAM_NONE, "BMMiner versions" },
    { SEVERITY_ERR,   MSG_INVJSON, PARAM_NONE, "Invalid JSON" },
    { SEVERITY_ERR,   MSG_MISCMD,  PARAM_CMD,  "Missing JSON '%s'" },
    { SEVERITY_ERR,   MSG_MISPID,  PARAM_NONE, "Missing pool id parameter" },
    { SEVERITY_ERR,   MSG_INVPID,  PARAM_POOLMAX,  "Invalid pool id %d - range is 0 - %d" },
    { SEVERITY_SUCC,  MSG_SWITCHP, PARAM_POOL, "Switching to pool %d:'%s'" },
    { SEVERITY_SUCC,  MSG_MINECONFIG,PARAM_NONE,   "BMMiner config" },
    { SEVERITY_ERR,   MSG_MISFN,   PARAM_NONE, "Missing save filename parameter" },
    { SEVERITY_ERR,   MSG_BADFN,   PARAM_STR,  "Can't open or create save file '%s'" },
    { SEVERITY_SUCC,  MSG_SAVED,   PARAM_STR,  "Configuration saved to file '%s'" },
    { SEVERITY_ERR,   MSG_ACCDENY, PARAM_STR,  "Access denied to '%s' command" },
    { SEVERITY_SUCC,  MSG_ACCOK,   PARAM_NONE, "Privileged access OK" },
    { SEVERITY_SUCC,  MSG_ENAPOOL, PARAM_POOL, "Enabling pool %d:'%s'" },
    { SEVERITY_SUCC,  MSG_POOLPRIO,PARAM_NONE, "Changed pool priorities" },
    { SEVERITY_ERR,   MSG_DUPPID,  PARAM_PID,  "Duplicate pool specified %d" },
    { SEVERITY_SUCC,  MSG_DISPOOL, PARAM_POOL, "Disabling pool %d:'%s'" },
    { SEVERITY_INFO,  MSG_ALRENAP, PARAM_POOL, "Pool %d:'%s' already enabled" },
    { SEVERITY_INFO,  MSG_ALRDISP, PARAM_POOL, "Pool %d:'%s' already disabled" },
    { SEVERITY_ERR,   MSG_DISLASTP,PARAM_POOL, "Cannot disable last active pool %d:'%s'" },
    { SEVERITY_ERR,   MSG_MISPDP,  PARAM_NONE, "Missing addpool details" },
    { SEVERITY_ERR,   MSG_INVPDP,  PARAM_STR,  "Invalid addpool details '%s'" },
    { SEVERITY_ERR,   MSG_TOOMANYP,PARAM_NONE, "Reached maximum number of pools (%d)" },
    { SEVERITY_SUCC,  MSG_ADDPOOL, PARAM_POOL, "Added pool %d: '%s'" },
    { SEVERITY_ERR,   MSG_REMLASTP,PARAM_POOL, "Cannot remove last pool %d:'%s'" },
    { SEVERITY_ERR,   MSG_ACTPOOL, PARAM_POOL, "Cannot remove active pool %d:'%s'" },
    { SEVERITY_SUCC,  MSG_REMPOOL, PARAM_BOTH, "Removed pool %d:'%s'" },
    { SEVERITY_SUCC,  MSG_NOTIFY,  PARAM_NONE, "Notify" },
    { SEVERITY_SUCC,  MSG_DEVDETAILS,PARAM_NONE,   "Device Details" },
    { SEVERITY_SUCC,  MSG_MINESTATS,PARAM_NONE,    "BMMiner stats" },
    { SEVERITY_ERR,   MSG_MISCHK,  PARAM_NONE, "Missing check cmd" },
    { SEVERITY_SUCC,  MSG_CHECK,   PARAM_NONE, "Check command" },
    { SEVERITY_ERR,   MSG_MISBOOL, PARAM_NONE, "Missing parameter: true/false" },
    { SEVERITY_ERR,   MSG_INVBOOL, PARAM_NONE, "Invalid parameter should be true or false" },
    { SEVERITY_SUCC,  MSG_FOO, PARAM_BOOL, "Failover-Only set to %s" },
    { SEVERITY_SUCC,  MSG_MINECOIN,PARAM_NONE, "BMMiner coin" },
    { SEVERITY_SUCC,  MSG_DEBUGSET,PARAM_NONE, "Debug settings" },
#ifdef HAVE_AN_FPGA
    { SEVERITY_SUCC,  MSG_PGAIDENT,PARAM_PGA,  "Identify command sent to PGA%d" },
    { SEVERITY_WARN,  MSG_PGANOID, PARAM_PGA,  "PGA%d does not support identify" },
#endif
    { SEVERITY_SUCC,  MSG_SETCONFIG,PARAM_SET, "Set config '%s' to %d" },
    { SEVERITY_ERR,   MSG_UNKCON,  PARAM_STR,  "Unknown config '%s'" },
    { SEVERITY_ERR,   MSG_INVNUM,  PARAM_BOTH, "Invalid number (%d) for '%s' range is 0-9999" },
    { SEVERITY_ERR,   MSG_INVNEG,  PARAM_BOTH, "Invalid negative number (%d) for '%s'" },
    { SEVERITY_SUCC,  MSG_SETQUOTA,PARAM_SET,  "Set pool '%s' to quota %d'" },
    { SEVERITY_ERR,   MSG_CONPAR,  PARAM_NONE, "Missing config parameters 'name,N'" },
    { SEVERITY_ERR,   MSG_CONVAL,  PARAM_STR,  "Missing config value N for '%s,N'" },
    { SEVERITY_SUCC,  MSG_USBSTA,  PARAM_NONE, "USB Statistics" },
    { SEVERITY_INFO,  MSG_NOUSTA,  PARAM_NONE, "No USB Statistics" },
#ifdef HAVE_AN_FPGA
    { SEVERITY_ERR,   MSG_MISPGAOPT, PARAM_NONE,   "Missing option after PGA number" },
    { SEVERITY_WARN,  MSG_PGANOSET, PARAM_PGA, "PGA %d does not support pgaset" },
    { SEVERITY_INFO,  MSG_PGAHELP, PARAM_BOTH, "PGA %d set help: %s" },
    { SEVERITY_SUCC,  MSG_PGASETOK, PARAM_BOTH,    "PGA %d set OK" },
    { SEVERITY_ERR,   MSG_PGASETERR, PARAM_BOTH,   "PGA %d set failed: %s" },
#endif
    { SEVERITY_ERR,   MSG_ZERMIS,  PARAM_NONE, "Missing zero parameters" },
    { SEVERITY_ERR,   MSG_ZERINV,  PARAM_STR,  "Invalid zero parameter '%s'" },
    { SEVERITY_SUCC,  MSG_ZERSUM,  PARAM_STR,  "Zeroed %s stats with summary" },
    { SEVERITY_SUCC,  MSG_ZERNOSUM, PARAM_STR, "Zeroed %s stats without summary" },
#ifdef USE_USBUTILS
    { SEVERITY_ERR,   MSG_PGAUSBNODEV, PARAM_PGA,  "PGA%d has no device" },
    { SEVERITY_ERR,   MSG_ASCUSBNODEV, PARAM_PGA,  "ASC%d has no device" },
#endif
    { SEVERITY_ERR,   MSG_INVHPLG, PARAM_STR,  "Invalid value for hotplug (%s) must be 0..9999" },
    { SEVERITY_SUCC,  MSG_HOTPLUG, PARAM_INT,  "Hotplug check set to %ds" },
    { SEVERITY_SUCC,  MSG_DISHPLG, PARAM_NONE, "Hotplug disabled" },
    { SEVERITY_WARN,  MSG_NOHPLG,  PARAM_NONE, "Hotplug is not available" },
    { SEVERITY_ERR,   MSG_MISHPLG, PARAM_NONE, "Missing hotplug parameter" },
#ifdef HAVE_AN_ASIC
    { SEVERITY_ERR,   MSG_ASCNON,  PARAM_NONE, "No ASCs" },
    { SEVERITY_SUCC,  MSG_ASCDEV,  PARAM_ASC,  "ASC%d" },
    { SEVERITY_ERR,   MSG_INVASC,  PARAM_ASCMAX,   "Invalid ASC id %d - range is 0 - %d" },
    { SEVERITY_INFO,  MSG_ASCLRENA,PARAM_ASC,  "ASC %d already enabled" },
    { SEVERITY_INFO,  MSG_ASCLRDIS,PARAM_ASC,  "ASC %d already disabled" },
    { SEVERITY_INFO,  MSG_ASCENA,  PARAM_ASC,  "ASC %d sent enable message" },
    { SEVERITY_INFO,  MSG_ASCDIS,  PARAM_ASC,  "ASC %d set disable flag" },
    { SEVERITY_ERR,   MSG_ASCUNW,  PARAM_ASC,  "ASC %d is not flagged WELL, cannot enable" },
    { SEVERITY_SUCC,  MSG_ASCIDENT,PARAM_ASC,  "Identify command sent to ASC%d" },
    { SEVERITY_WARN,  MSG_ASCNOID, PARAM_ASC,  "ASC%d does not support identify" },
    { SEVERITY_ERR,   MSG_MISASCOPT, PARAM_NONE,   "Missing option after ASC number" },
    { SEVERITY_WARN,  MSG_ASCNOSET, PARAM_ASC, "ASC %d does not support ascset" },
    { SEVERITY_INFO,  MSG_ASCHELP, PARAM_BOTH, "ASC %d set help: %s" },
    { SEVERITY_SUCC,  MSG_ASCSETOK, PARAM_BOTH,    "ASC %d set OK" },
    { SEVERITY_ERR,   MSG_ASCSETERR, PARAM_BOTH,   "ASC %d set failed: %s" },
#endif
    { SEVERITY_SUCC,  MSG_LCD, PARAM_NONE, "LCD" },
    { SEVERITY_SUCC,  MSG_LOCKOK,  PARAM_NONE, "Lock stats created" },
    { SEVERITY_WARN,  MSG_LOCKDIS, PARAM_NONE, "Lock stats not enabled" },
    { SEVERITY_FAIL, 0, 0, NULL }
};

static const char *localaddr = "127.0.0.1";

static int my_thr_id = 0;
static bool bye;

// Used to control quit restart access to shutdown variables
static pthread_mutex_t quit_restart_lock;

static bool do_a_quit;
static bool do_a_restart;

static time_t when = 0; // when the request occurred

struct IPACCESS
{
    struct in6_addr ip;
    struct in6_addr mask;
    char group;
};

#define GROUP(g) (toupper(g))
#define PRIVGROUP GROUP('W')
#define NOPRIVGROUP GROUP('R')
#define ISPRIVGROUP(g) (GROUP(g) == PRIVGROUP)
#define GROUPOFFSET(g) (GROUP(g) - GROUP('A'))
#define VALIDGROUP(g) (GROUP(g) >= GROUP('A') && GROUP(g) <= GROUP('Z'))
#define COMMANDS(g) (apigroups[GROUPOFFSET(g)].commands)
#define DEFINEDGROUP(g) (ISPRIVGROUP(g) || COMMANDS(g) != NULL)

struct APIGROUPS
{
    // This becomes a string like: "|cmd1|cmd2|cmd3|" so it's quick to search
    char *commands;
} apigroups['Z' - 'A' + 1]; // only A=0 to Z=25 (R: noprivs, W: allprivs)

static struct IPACCESS *ipaccess = NULL;
static int ips = 0;

struct io_data
{
    size_t siz;
    char *ptr;
    char *cur;
    bool sock;
    bool close;
};

struct io_list
{
    struct io_data *io_data;
    struct io_list *prev;
    struct io_list *next;
};

static struct io_list *io_head = NULL;

#define SOCKBUFALLOCSIZ 65536

#define io_new(init) _io_new(init, false)
#define sock_io_new() _io_new(SOCKBUFALLOCSIZ, true)

#define ALLOC_SBITEMS 2
#define LIMIT_SBITEMS 0

typedef struct sbitem
{
    char *buf;
    size_t siz;
    size_t tot;
} SBITEM;

// Size to grow tot if exceeded
#define SBEXTEND 4096

#define DATASB(_item) ((SBITEM *)(_item->data))

static K_LIST *strbufs;

static void io_reinit(struct io_data *io_data)
{
    io_data->cur = io_data->ptr;
    *(io_data->ptr) = '\0';
    io_data->close = false;
}

static struct io_data *_io_new(size_t initial, bool socket_buf)
{
    struct io_data *io_data;
    struct io_list *io_list;

    io_data = malloc(sizeof(*io_data));
    io_data->ptr = malloc(initial);
    io_data->siz = initial;
    io_data->sock = socket_buf;
    io_reinit(io_data);

    io_list = malloc(sizeof(*io_list));

    io_list->io_data = io_data;

    if (io_head)
    {
        io_list->next = io_head;
        io_list->prev = io_head->prev;
        io_list->next->prev = io_list;
        io_list->prev->next = io_list;
    }
    else
    {
        io_list->prev = io_list;
        io_list->next = io_list;
        io_head = io_list;
    }

    return io_data;
}

static bool io_add(struct io_data *io_data, char *buf)
{
    size_t len, dif, tot;

    len = strlen(buf);
    dif = io_data->cur - io_data->ptr;
    // send will always have enough space to add the JSON
    tot = len + 1 + dif + sizeof(JSON_CLOSE) + sizeof(JSON_END);

    if (tot > io_data->siz)
    {
        size_t new = io_data->siz + (2 * SOCKBUFALLOCSIZ);

        if (new < tot)
            new = (2 + (size_t)((float)tot / (float)SOCKBUFALLOCSIZ)) * SOCKBUFALLOCSIZ;

        io_data->ptr = realloc(io_data->ptr, new);
        io_data->cur = io_data->ptr + dif;
        io_data->siz = new;
    }

    memcpy(io_data->cur, buf, len + 1);
    io_data->cur += len;

    return true;
}

static bool io_put(struct io_data *io_data, char *buf)
{
    io_reinit(io_data);
    return io_add(io_data, buf);
}

static void io_close(struct io_data *io_data)
{
    io_data->close = true;
}

static void io_free()
{
    struct io_list *io_list, *io_next;

    if (io_head)
    {
        io_list = io_head;
        do
        {
            io_next = io_list->next;

            free(io_list->io_data->ptr);
            free(io_list->io_data);
            free(io_list);

            io_list = io_next;
        }
        while (io_list != io_head);

        io_head = NULL;
    }
}

// This is only called when expected to be needed (rarely)
// i.e. strings outside of the codes control (input from the user)
static char *escape_string(char *str, bool isjson)
{
    char *buf, *ptr;
    int count;

    count = 0;
    for (ptr = str; *ptr; ptr++)
    {
        switch (*ptr)
        {
            case ',':
            case '|':
            case '=':
                if (!isjson)
                    count++;
                break;
            case '"':
                if (isjson)
                    count++;
                break;
            case '\\':
                count++;
                break;
        }
    }

    if (count == 0)
        return str;

    buf = malloc(strlen(str) + count + 1);
    if (unlikely(!buf))
    {
        quithere(1, "Failed to malloc escape buf %d",
                 (int)(strlen(str) + count + 1));
    }

    ptr = buf;
    while (*str)
        switch (*str)
        {
            case ',':
            case '|':
            case '=':
                if (!isjson)
                    *(ptr++) = '\\';
                *(ptr++) = *(str++);
                break;
            case '"':
                if (isjson)
                    *(ptr++) = '\\';
                *(ptr++) = *(str++);
                break;
            case '\\':
                *(ptr++) = '\\';
                *(ptr++) = *(str++);
                break;
            default:
                *(ptr++) = *(str++);
                break;
        }

    *ptr = '\0';

    return buf;
}

static struct api_data *api_add_extra(struct api_data *root, struct api_data *extra)
{
    struct api_data *tmp;

    if (root)
    {
        if (extra)
        {
            // extra tail
            tmp = extra->prev;

            // extra prev = root tail
            extra->prev = root->prev;

            // root tail next = extra
            root->prev->next = extra;

            // extra tail next = root
            tmp->next = root;

            // root prev = extra tail
            root->prev = tmp;
        }
    }
    else
        root = extra;

    return root;
}

static struct api_data *api_add_data_full(struct api_data *root, char *name, enum api_data_type type, void *data, bool copy_data)
{
    struct api_data *api_data;

    api_data = (struct api_data *)malloc(sizeof(struct api_data));

    api_data->name = strdup(name);
    api_data->type = type;

    if (root == NULL)
    {
        root = api_data;
        root->prev = root;
        root->next = root;
    }
    else
    {
        api_data->prev = root->prev;
        root->prev = api_data;
        api_data->next = root;
        api_data->prev->next = api_data;
    }

    api_data->data_was_malloc = copy_data;

    // Avoid crashing on bad data
    if (data == NULL)
    {
        api_data->type = type = API_CONST;
        data = (void *)NULLSTR;
        api_data->data_was_malloc = copy_data = false;
    }

    if (!copy_data)
        api_data->data = data;
    else
        switch(type)
        {
            case API_ESCAPE:
            case API_STRING:
            case API_CONST:
                api_data->data = (void *)malloc(strlen((char *)data) + 1);
                strcpy((char*)(api_data->data), (char *)data);
                break;
            case API_UINT8:
                /* Most OSs won't really alloc less than 4 */
                api_data->data = malloc(4);
                *(uint8_t *)api_data->data = *(uint8_t *)data;
                break;
            case API_INT16:
                /* Most OSs won't really alloc less than 4 */
                api_data->data = malloc(4);
                *(int16_t *)api_data->data = *(int16_t *)data;
                break;
            case API_UINT16:
                /* Most OSs won't really alloc less than 4 */
                api_data->data = malloc(4);
                *(uint16_t *)api_data->data = *(uint16_t *)data;
                break;
            case API_INT:
                api_data->data = (void *)malloc(sizeof(int));
                *((int *)(api_data->data)) = *((int *)data);
                break;
            case API_UINT:
                api_data->data = (void *)malloc(sizeof(unsigned int));
                *((unsigned int *)(api_data->data)) = *((unsigned int *)data);
                break;
            case API_UINT32:
                api_data->data = (void *)malloc(sizeof(uint32_t));
                *((uint32_t *)(api_data->data)) = *((uint32_t *)data);
                break;
            case API_HEX32:
                api_data->data = (void *)malloc(sizeof(uint32_t));
                *((uint32_t *)(api_data->data)) = *((uint32_t *)data);
                break;
            case API_UINT64:
                api_data->data = (void *)malloc(sizeof(uint64_t));
                *((uint64_t *)(api_data->data)) = *((uint64_t *)data);
                break;
            case API_INT64:
                api_data->data = (void *)malloc(sizeof(int64_t));
                *((int64_t *)(api_data->data)) = *((int64_t *)data);
                break;
            case API_DOUBLE:
            case API_ELAPSED:
            case API_MHS:
            case API_MHTOTAL:
            case API_UTILITY:
            case API_FREQ:
            case API_HS:
            case API_DIFF:
            case API_PERCENT:
                api_data->data = (void *)malloc(sizeof(double));
                *((double *)(api_data->data)) = *((double *)data);
                break;
            case API_BOOL:
                api_data->data = (void *)malloc(sizeof(bool));
                *((bool *)(api_data->data)) = *((bool *)data);
                break;
            case API_TIMEVAL:
                api_data->data = (void *)malloc(sizeof(struct timeval));
                memcpy(api_data->data, data, sizeof(struct timeval));
                break;
            case API_TIME:
                api_data->data = (void *)malloc(sizeof(time_t));
                *(time_t *)(api_data->data) = *((time_t *)data);
                break;
            case API_VOLTS:
            case API_TEMP:
            case API_AVG:
                api_data->data = (void *)malloc(sizeof(float));
                *((float *)(api_data->data)) = *((float *)data);
                break;
            default:
                applog(LOG_ERR, "API: unknown1 data type %d ignored", type);
                api_data->type = API_STRING;
                api_data->data_was_malloc = false;
                api_data->data = (void *)UNKNOWN;
                break;
        }

    return root;
}

struct api_data *api_add_escape(struct api_data *root, char *name, char *data, bool copy_data)
{
    return api_add_data_full(root, name, API_ESCAPE, (void *)data, copy_data);
}

struct api_data *api_add_string(struct api_data *root, char *name, char *data, bool copy_data)
{
    return api_add_data_full(root, name, API_STRING, (void *)data, copy_data);
}

struct api_data *api_add_const(struct api_data *root, char *name, const char *data, bool copy_data)
{
    return api_add_data_full(root, name, API_CONST, (void *)data, copy_data);
}

struct api_data *api_add_uint8(struct api_data *root, char *name, uint8_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_UINT8, (void *)data, copy_data);
}

struct api_data *api_add_int16(struct api_data *root, char *name, uint16_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_INT16, (void *)data, copy_data);
}

struct api_data *api_add_uint16(struct api_data *root, char *name, uint16_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_UINT16, (void *)data, copy_data);
}

struct api_data *api_add_int(struct api_data *root, char *name, int *data, bool copy_data)
{
    return api_add_data_full(root, name, API_INT, (void *)data, copy_data);
}

struct api_data *api_add_uint(struct api_data *root, char *name, unsigned int *data, bool copy_data)
{
    return api_add_data_full(root, name, API_UINT, (void *)data, copy_data);
}

struct api_data *api_add_uint32(struct api_data *root, char *name, uint32_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_UINT32, (void *)data, copy_data);
}

struct api_data *api_add_hex32(struct api_data *root, char *name, uint32_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_HEX32, (void *)data, copy_data);
}

struct api_data *api_add_uint64(struct api_data *root, char *name, uint64_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_UINT64, (void *)data, copy_data);
}

struct api_data *api_add_int64(struct api_data *root, char *name, int64_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_INT64, (void *)data, copy_data);
}

struct api_data *api_add_double(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_DOUBLE, (void *)data, copy_data);
}

struct api_data *api_add_elapsed(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_ELAPSED, (void *)data, copy_data);
}

struct api_data *api_add_bool(struct api_data *root, char *name, bool *data, bool copy_data)
{
    return api_add_data_full(root, name, API_BOOL, (void *)data, copy_data);
}

struct api_data *api_add_timeval(struct api_data *root, char *name, struct timeval *data, bool copy_data)
{
    return api_add_data_full(root, name, API_TIMEVAL, (void *)data, copy_data);
}

struct api_data *api_add_time(struct api_data *root, char *name, time_t *data, bool copy_data)
{
    return api_add_data_full(root, name, API_TIME, (void *)data, copy_data);
}

struct api_data *api_add_mhs(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_MHS, (void *)data, copy_data);
}

struct api_data *api_add_mhtotal(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_MHTOTAL, (void *)data, copy_data);
}

struct api_data *api_add_temp(struct api_data *root, char *name, float *data, bool copy_data)
{
    return api_add_data_full(root, name, API_TEMP, (void *)data, copy_data);
}

struct api_data *api_add_utility(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_UTILITY, (void *)data, copy_data);
}

struct api_data *api_add_freq(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_FREQ, (void *)data, copy_data);
}

struct api_data *api_add_volts(struct api_data *root, char *name, float *data, bool copy_data)
{
    return api_add_data_full(root, name, API_VOLTS, (void *)data, copy_data);
}

struct api_data *api_add_hs(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_HS, (void *)data, copy_data);
}

struct api_data *api_add_diff(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_DIFF, (void *)data, copy_data);
}

struct api_data *api_add_percent(struct api_data *root, char *name, double *data, bool copy_data)
{
    return api_add_data_full(root, name, API_PERCENT, (void *)data, copy_data);
}

struct api_data *api_add_avg(struct api_data *root, char *name, float *data, bool copy_data)
{
    return api_add_data_full(root, name, API_AVG, (void *)data, copy_data);
}

static void add_item_buf(K_ITEM *item, const char *str)
{
    size_t old_siz, new_siz, siz, ext;
    char *buf;

    buf = DATASB(item)->buf;
    siz = (size_t)strlen(str);

    old_siz = DATASB(item)->siz;
    new_siz = old_siz + siz + 1; // include '\0'
    if (DATASB(item)->tot < new_siz)
    {
        ext = (siz + 1) + SBEXTEND - ((siz + 1) % SBEXTEND);
        DATASB(item)->buf = buf = realloc(DATASB(item)->buf, DATASB(item)->tot + ext);
        if (!buf)
        {
            quithere(1, "OOM buf siz=%d tot=%d ext=%d",
                     (int)siz, (int)(DATASB(item)->tot), (int)ext);
        }
        DATASB(item)->tot += ext;
    }
    memcpy(buf + old_siz, str, siz + 1);
    DATASB(item)->siz += siz;
}

static struct api_data *print_data(struct io_data *io_data, struct api_data *root, bool isjson, bool precom)
{
    // N.B. strings don't use this buffer so 64 is enough (for now)
    char buf[64];
    struct api_data *tmp;
    bool done, first = true;
    char *original, *escape;
    K_ITEM *item;

    K_WLOCK(strbufs);
    item = k_unlink_head(strbufs);
    K_WUNLOCK(strbufs);

    DATASB(item)->siz = 0;

    if (precom)
        add_item_buf(item, COMMA);

    if (isjson)
        add_item_buf(item, JSON0);

    while (root)
    {
        if (!first)
            add_item_buf(item, COMMA);
        else
            first = false;

        if (isjson)
            add_item_buf(item, JSON1);

        add_item_buf(item, root->name);

        if (isjson)
            add_item_buf(item, JSON1);

        if (isjson)
            add_item_buf(item, ":");
        else
            add_item_buf(item, "=");

        first = false;

        done = false;
        switch(root->type)
        {
            case API_STRING:
            case API_CONST:
                if (isjson)
                    add_item_buf(item, JSON1);
                add_item_buf(item, (char *)(root->data));
                if (isjson)
                    add_item_buf(item, JSON1);
                done = true;
                break;
            case API_ESCAPE:
                original = (char *)(root->data);
                escape = escape_string((char *)(root->data), isjson);
                if (isjson)
                    add_item_buf(item, JSON1);
                add_item_buf(item, escape);
                if (isjson)
                    add_item_buf(item, JSON1);
                if (escape != original)
                    free(escape);
                done = true;
                break;
            case API_UINT8:
                snprintf(buf, sizeof(buf), "%u", *(uint8_t *)root->data);
                break;
            case API_INT16:
                snprintf(buf, sizeof(buf), "%d", *(int16_t *)root->data);
                break;
            case API_UINT16:
                snprintf(buf, sizeof(buf), "%u", *(uint16_t *)root->data);
                break;
            case API_INT:
                snprintf(buf, sizeof(buf), "%d", *((int *)(root->data)));
                break;
            case API_UINT:
                snprintf(buf, sizeof(buf), "%u", *((unsigned int *)(root->data)));
                break;
            case API_UINT32:
                snprintf(buf, sizeof(buf), "%"PRIu32, *((uint32_t *)(root->data)));
                break;
            case API_HEX32:
                if (isjson)
                    add_item_buf(item, JSON1);
                snprintf(buf, sizeof(buf), "0x%08x", *((uint32_t *)(root->data)));
                add_item_buf(item, buf);
                if (isjson)
                    add_item_buf(item, JSON1);
                done = true;
                break;
            case API_UINT64:
                snprintf(buf, sizeof(buf), "%"PRIu64, *((uint64_t *)(root->data)));
                break;
            case API_INT64:
                snprintf(buf, sizeof(buf), "%"PRId64, *((int64_t *)(root->data)));
                break;
            case API_TIME:
                snprintf(buf, sizeof(buf), "%lu", *((unsigned long *)(root->data)));
                break;
            case API_DOUBLE:
                snprintf(buf, sizeof(buf), "%f", *((double *)(root->data)));
                break;
            case API_ELAPSED:
                snprintf(buf, sizeof(buf), "%.0f", *((double *)(root->data)));
                break;
            case API_UTILITY:
            case API_FREQ:
            case API_MHS:
                snprintf(buf, sizeof(buf), "%.2f", *((double *)(root->data)));
                break;
            case API_VOLTS:
            case API_AVG:
                snprintf(buf, sizeof(buf), "%.3f", *((float *)(root->data)));
                break;
            case API_MHTOTAL:
                snprintf(buf, sizeof(buf), "%.4f", *((double *)(root->data)));
                break;
            case API_HS:
                snprintf(buf, sizeof(buf), "%.15f", *((double *)(root->data)));
                break;
            case API_DIFF:
                snprintf(buf, sizeof(buf), "%.8f", *((double *)(root->data)));
                break;
            case API_BOOL:
                snprintf(buf, sizeof(buf), "%s", *((bool *)(root->data)) ? TRUESTR : FALSESTR);
                break;
            case API_TIMEVAL:
                snprintf(buf, sizeof(buf), "%ld.%06ld",
                         (long)((struct timeval *)(root->data))->tv_sec,
                         (long)((struct timeval *)(root->data))->tv_usec);
                break;
            case API_TEMP:
                snprintf(buf, sizeof(buf), "%.2f", *((float *)(root->data)));
                break;
            case API_PERCENT:
                snprintf(buf, sizeof(buf), "%.4f", *((double *)(root->data)) * 100.0);
                break;
            default:
                applog(LOG_ERR, "API: unknown2 data type %d ignored", root->type);
                if (isjson)
                    add_item_buf(item, JSON1);
                add_item_buf(item, UNKNOWN);
                if (isjson)
                    add_item_buf(item, JSON1);
                done = true;
                break;
        }

        if (!done)
            add_item_buf(item, buf);

        free(root->name);
        if (root->data_was_malloc)
            free(root->data);

        if (root->next == root)
        {
            free(root);
            root = NULL;
        }
        else
        {
            tmp = root;
            root = tmp->next;
            root->prev = tmp->prev;
            root->prev->next = root;
            free(tmp);
        }
    }

    if (isjson)
        add_item_buf(item, JSON5);
    else
        add_item_buf(item, SEPSTR);

    io_add(io_data, DATASB(item)->buf);

    K_WLOCK(strbufs);
    k_add_head(strbufs, item);
    K_WUNLOCK(strbufs);

    return root;
}

#define DRIVER_COUNT_DRV(X) if (devices[i]->drv->drv_id == DRIVER_##X) \
    count++;

#ifdef HAVE_AN_ASIC
static int numascs(void)
{
    int count = 0;
    int i;

    rd_lock(&devices_lock);
    for (i = 0; i < total_devices; i++)
    {
        ASIC_PARSE_COMMANDS(DRIVER_COUNT_DRV)
    }
    rd_unlock(&devices_lock);
    return count;
}

static int ascdevice(int ascid)
{
    int count = 0;
    int i;

    rd_lock(&devices_lock);
    for (i = 0; i < total_devices; i++)
    {
        ASIC_PARSE_COMMANDS(DRIVER_COUNT_DRV)
        if (count == (ascid + 1))
            goto foundit;
    }

    rd_unlock(&devices_lock);
    return -1;

foundit:

    rd_unlock(&devices_lock);
    return i;
}
#endif

#ifdef HAVE_AN_FPGA
static int numpgas(void)
{
    int count = 0;
    int i;

    rd_lock(&devices_lock);
    for (i = 0; i < total_devices; i++)
    {
        FPGA_PARSE_COMMANDS(DRIVER_COUNT_DRV)
    }
    rd_unlock(&devices_lock);
    return count;
}

static int pgadevice(int pgaid)
{
    int count = 0;
    int i;

    rd_lock(&devices_lock);
    for (i = 0; i < total_devices; i++)
    {
        FPGA_PARSE_COMMANDS(DRIVER_COUNT_DRV)
        if (count == (pgaid + 1))
            goto foundit;
    }

    rd_unlock(&devices_lock);
    return -1;

foundit:

    rd_unlock(&devices_lock);
    return i;
}
#endif

// All replies (except BYE and RESTART) start with a message
//  thus for JSON, message() inserts JSON_START at the front
//  and send_result() adds JSON_END at the end
static void message(struct io_data *io_data, int messageid, int paramid, char *param2, bool isjson)
{
    struct api_data *root = NULL;
    char buf[TMPBUFSIZ];
    char severity[2];
#ifdef HAVE_AN_ASIC
    int asc;
#endif
#ifdef HAVE_AN_FPGA
    int pga;
#endif
    int i;

    if (isjson)
        io_add(io_data, JSON_START JSON_STATUS);

    for (i = 0; codes[i].severity != SEVERITY_FAIL; i++)
    {
        if (codes[i].code == messageid)
        {
            switch (codes[i].severity)
            {
                case SEVERITY_WARN:
                    severity[0] = 'W';
                    break;
                case SEVERITY_INFO:
                    severity[0] = 'I';
                    break;
                case SEVERITY_SUCC:
                    severity[0] = 'S';
                    break;
                case SEVERITY_ERR:
                default:
                    severity[0] = 'E';
                    break;
            }
            severity[1] = '\0';

            switch(codes[i].params)
            {
                case PARAM_PGA:
                case PARAM_ASC:
                case PARAM_PID:
                case PARAM_INT:
                    sprintf(buf, codes[i].description, paramid);
                    break;
                case PARAM_POOL:
                    sprintf(buf, codes[i].description, paramid, pools[paramid]->rpc_url);
                    break;
#ifdef HAVE_AN_FPGA
                case PARAM_PGAMAX:
                    pga = numpgas();
                    sprintf(buf, codes[i].description, paramid, pga - 1);
                    break;
#endif
#ifdef HAVE_AN_ASIC
                case PARAM_ASCMAX:
                    asc = numascs();
                    sprintf(buf, codes[i].description, paramid, asc - 1);
                    break;
#endif
                case PARAM_PMAX:
                    sprintf(buf, codes[i].description, total_pools);
                    break;
                case PARAM_POOLMAX:
                    sprintf(buf, codes[i].description, paramid, total_pools - 1);
                    break;
                case PARAM_DMAX:
#ifdef HAVE_AN_ASIC
                    asc = numascs();
#endif
#ifdef HAVE_AN_FPGA
                    pga = numpgas();
#endif

                    sprintf(buf, codes[i].description
#ifdef HAVE_AN_ASIC
                            , asc
#endif
#ifdef HAVE_AN_FPGA
                            , pga
#endif
                           );
                    break;
                case PARAM_CMD:
                    sprintf(buf, codes[i].description, JSON_COMMAND);
                    break;
                case PARAM_STR:
                    sprintf(buf, codes[i].description, param2);
                    break;
                case PARAM_BOTH:
                    sprintf(buf, codes[i].description, paramid, param2);
                    break;
                case PARAM_BOOL:
                    sprintf(buf, codes[i].description, paramid ? TRUESTR : FALSESTR);
                    break;
                case PARAM_SET:
                    sprintf(buf, codes[i].description, param2, paramid);
                    break;
                case PARAM_NONE:
                default:
                    strcpy(buf, codes[i].description);
            }

            root = api_add_string(root, _STATUS, severity, false);
            root = api_add_time(root, "When", &when, false);
            root = api_add_int(root, "Code", &messageid, false);
            root = api_add_escape(root, "Msg", buf, false);
            root = api_add_escape(root, "Description", opt_api_description, false);

            root = print_data(io_data, root, isjson, false);
            if (isjson)
                io_add(io_data, JSON_CLOSE);
            return;
        }
    }

    root = api_add_string(root, _STATUS, "F", false);
    root = api_add_time(root, "When", &when, false);
    int id = -1;
    root = api_add_int(root, "Code", &id, false);
    sprintf(buf, "%d", messageid);
    root = api_add_escape(root, "Msg", buf, false);
    root = api_add_escape(root, "Description", opt_api_description, false);

    root = print_data(io_data, root, isjson, false);
    if (isjson)
        io_add(io_data, JSON_CLOSE);
}

#if LOCK_TRACKING

#define LOCK_FMT_FFL " - called from %s %s():%d"

#define LOCKMSG(fmt, ...)   fprintf(stderr, "APILOCK: " fmt "\n", ##__VA_ARGS__)
#define LOCKMSGMORE(fmt, ...)   fprintf(stderr, "          " fmt "\n", ##__VA_ARGS__)
#define LOCKMSGFFL(fmt, ...) fprintf(stderr, "APILOCK: " fmt LOCK_FMT_FFL "\n", ##__VA_ARGS__, file, func, linenum)
#define LOCKMSGFLUSH() fflush(stderr)

typedef struct lockstat
{
    uint64_t lock_id;
    const char *file;
    const char *func;
    int linenum;
    struct timeval tv;
} LOCKSTAT;

typedef struct lockline
{
    struct lockline *prev;
    struct lockstat *stat;
    struct lockline *next;
} LOCKLINE;

typedef struct lockinfo
{
    void *lock;
    enum cglock_typ typ;
    const char *file;
    const char *func;
    int linenum;
    uint64_t gets;
    uint64_t gots;
    uint64_t tries;
    uint64_t dids;
    uint64_t didnts; // should be tries - dids
    uint64_t unlocks;
    LOCKSTAT lastgot;
    LOCKLINE *lockgets;
    LOCKLINE *locktries;
} LOCKINFO;

typedef struct locklist
{
    LOCKINFO *info;
    struct locklist *next;
} LOCKLIST;

static uint64_t lock_id = 1;

static LOCKLIST *lockhead;

static void lockmsgnow()
{
    struct timeval now;
    struct tm *tm;
    time_t dt;

    cgtime(&now);

    dt = now.tv_sec;
    tm = localtime(&dt);

    LOCKMSG("%d-%02d-%02d %02d:%02d:%02d",
            tm->tm_year + 1900,
            tm->tm_mon + 1,
            tm->tm_mday,
            tm->tm_hour,
            tm->tm_min,
            tm->tm_sec);
}

static LOCKLIST *newlock(void *lock, enum cglock_typ typ, const char *file, const char *func, const int linenum)
{
    LOCKLIST *list;

    list = calloc(1, sizeof(*list));
    if (!list)
        quithere(1, "OOM list");
    list->info = calloc(1, sizeof(*(list->info)));
    if (!list->info)
        quithere(1, "OOM info");
    list->next = lockhead;
    lockhead = list;

    list->info->lock = lock;
    list->info->typ = typ;
    list->info->file = file;
    list->info->func = func;
    list->info->linenum = linenum;

    return list;
}

static LOCKINFO *findlock(void *lock, enum cglock_typ typ, const char *file, const char *func, const int linenum)
{
    LOCKLIST *look;

    look = lockhead;
    while (look)
    {
        if (look->info->lock == lock)
            break;
        look = look->next;
    }

    if (!look)
        look = newlock(lock, typ, file, func, linenum);

    return look->info;
}

static void addgettry(LOCKINFO *info, uint64_t id, const char *file, const char *func, const int linenum, bool get)
{
    LOCKSTAT *stat;
    LOCKLINE *line;

    stat = calloc(1, sizeof(*stat));
    if (!stat)
        quithere(1, "OOM stat");
    line = calloc(1, sizeof(*line));
    if (!line)
        quithere(1, "OOM line");

    if (get)
        info->gets++;
    else
        info->tries++;

    stat->lock_id = id;
    stat->file = file;
    stat->func = func;
    stat->linenum = linenum;
    cgtime(&stat->tv);

    line->stat = stat;

    if (get)
    {
        line->next = info->lockgets;
        if (info->lockgets)
            info->lockgets->prev = line;
        info->lockgets = line;
    }
    else
    {
        line->next = info->locktries;
        if (info->locktries)
            info->locktries->prev = line;
        info->locktries = line;
    }
}

static void markgotdid(LOCKINFO *info, uint64_t id, const char *file, const char *func, const int linenum, bool got, int ret)
{
    LOCKLINE *line;

    if (got)
        info->gots++;
    else
    {
        if (ret == 0)
            info->dids++;
        else
            info->didnts++;
    }

    if (got || ret == 0)
    {
        info->lastgot.lock_id = id;
        info->lastgot.file = file;
        info->lastgot.func = func;
        info->lastgot.linenum = linenum;
        cgtime(&info->lastgot.tv);
    }

    if (got)
        line = info->lockgets;
    else
        line = info->locktries;
    while (line)
    {
        if (line->stat->lock_id == id)
            break;
        line = line->next;
    }

    if (!line)
    {
        lockmsgnow();
        LOCKMSGFFL("ERROR attempt to mark a lock as '%s' that wasn't '%s' id=%"PRIu64,
                   got ? "got" : "did/didnt", got ? "get" : "try", id);
    }

    // Unlink it
    if (line->prev)
        line->prev->next = line->next;
    if (line->next)
        line->next->prev = line->prev;

    if (got)
    {
        if (info->lockgets == line)
            info->lockgets = line->next;
    }
    else
    {
        if (info->locktries == line)
            info->locktries = line->next;
    }

    free(line->stat);
    free(line);
}

// Yes this uses locks also ... ;/
static void locklock()
{
    if (unlikely(pthread_mutex_lock(&lockstat_lock)))
        quithere(1, "WTF MUTEX ERROR ON LOCK! errno=%d", errno);
}

static void lockunlock()
{
    if (unlikely(pthread_mutex_unlock(&lockstat_lock)))
        quithere(1, "WTF MUTEX ERROR ON UNLOCK! errno=%d", errno);
}

uint64_t api_getlock(void *lock, const char *file, const char *func, const int linenum)
{
    LOCKINFO *info;
    uint64_t id;

    locklock();

    info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);
    id = lock_id++;
    addgettry(info, id, file, func, linenum, true);

    lockunlock();

    return id;
}

void api_gotlock(uint64_t id, void *lock, const char *file, const char *func, const int linenum)
{
    LOCKINFO *info;

    locklock();

    info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);
    markgotdid(info, id, file, func, linenum, true, 0);

    lockunlock();
}

uint64_t api_trylock(void *lock, const char *file, const char *func, const int linenum)
{
    LOCKINFO *info;
    uint64_t id;

    locklock();

    info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);
    id = lock_id++;
    addgettry(info, id, file, func, linenum, false);

    lockunlock();

    return id;
}

void api_didlock(uint64_t id, int ret, void *lock, const char *file, const char *func, const int linenum)
{
    LOCKINFO *info;

    locklock();

    info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);
    markgotdid(info, id, file, func, linenum, false, ret);

    lockunlock();
}

void api_gunlock(void *lock, const char *file, const char *func, const int linenum)
{
    LOCKINFO *info;

    locklock();

    info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);
    info->unlocks++;

    lockunlock();
}

void api_initlock(void *lock, enum cglock_typ typ, const char *file, const char *func, const int linenum)
{
    locklock();

    findlock(lock, typ, file, func, linenum);

    lockunlock();
}

void dsp_det(char *msg, LOCKSTAT *stat)
{
    struct tm *tm;
    time_t dt;

    dt = stat->tv.tv_sec;
    tm = localtime(&dt);

    LOCKMSGMORE("%s id=%"PRIu64" by %s %s():%d at %d-%02d-%02d %02d:%02d:%02d",
                msg,
                stat->lock_id,
                stat->file,
                stat->func,
                stat->linenum,
                tm->tm_year + 1900,
                tm->tm_mon + 1,
                tm->tm_mday,
                tm->tm_hour,
                tm->tm_min,
                tm->tm_sec);
}

void dsp_lock(LOCKINFO *info)
{
    LOCKLINE *line;
    char *status;

    LOCKMSG("Lock %p created by %s %s():%d",
            info->lock,
            info->file,
            info->func,
            info->linenum);
    LOCKMSGMORE("gets:%"PRIu64" gots:%"PRIu64" tries:%"PRIu64
                " dids:%"PRIu64" didnts:%"PRIu64" unlocks:%"PRIu64,
                info->gets,
                info->gots,
                info->tries,
                info->dids,
                info->didnts,
                info->unlocks);

    if (info->gots > 0 || info->dids > 0)
    {
        if (info->unlocks < info->gots + info->dids)
            status = "Last got/did still HELD";
        else
            status = "Last got/did (idle)";

        dsp_det(status, &(info->lastgot));
    }
    else
        LOCKMSGMORE("... unused ...");

    if (info->lockgets)
    {
        LOCKMSGMORE("BLOCKED gets (%"PRIu64")", info->gets - info->gots);
        line = info->lockgets;
        while (line)
        {
            dsp_det("", line->stat);
            line = line->next;
        }
    }
    else
        LOCKMSGMORE("no blocked gets");

    if (info->locktries)
    {
        LOCKMSGMORE("BLOCKED tries (%"PRIu64")", info->tries - info->dids - info->didnts);
        line = info->lockgets;
        while (line)
        {
            dsp_det("", line->stat);
            line = line->next;
        }
    }
    else
        LOCKMSGMORE("no blocked tries");
}

void show_locks()
{
    LOCKLIST *list;

    locklock();

    lockmsgnow();

    list = lockhead;
    if (!list)
        LOCKMSG("no locks?!?\n");
    else
    {
        while (list)
        {
            dsp_lock(list->info);
            list = list->next;
        }
    }

    LOCKMSGFLUSH();

    lockunlock();
}
#endif

static void lockstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
#if LOCK_TRACKING
    show_locks();
    message(io_data, MSG_LOCKOK, 0, NULL, isjson);
#else
    message(io_data, MSG_LOCKDIS, 0, NULL, isjson);
#endif
}

static void apiversion(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;

    message(io_data, MSG_VERSION, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_VERSION : _VERSION COMSTR);

    root = api_add_string(root, "BMMiner", VERSION, false);
    root = api_add_const(root, "API", APIVERSION, false);
    root = api_add_string(root, "Miner", g_miner_version, false);
    root = api_add_string(root, "CompileTime", g_miner_compiletime, false);
    root = api_add_string(root, "Type", g_miner_type, false);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static void minerconfig(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;
    int asccount = 0;
    int pgacount = 0;

#ifdef HAVE_AN_ASIC
    asccount = numascs();
#endif

#ifdef HAVE_AN_FPGA
    pgacount = numpgas();
#endif

    message(io_data, MSG_MINECONFIG, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_MINECONFIG : _MINECONFIG COMSTR);

    root = api_add_int(root, "ASC Count", &asccount, false);
    root = api_add_int(root, "PGA Count", &pgacount, false);
    root = api_add_int(root, "Pool Count", &total_pools, false);
    root = api_add_const(root, "Strategy", strategies[pool_strategy].s, false);
    root = api_add_int(root, "Log Interval", &opt_log_interval, false);
    root = api_add_const(root, "Device Code", DEVICECODE, false);
    root = api_add_const(root, "OS", OSINFO, false);
    root = api_add_bool(root, "Failover-Only", &opt_fail_only, false);
    root = api_add_int(root, "ScanTime", &opt_scantime, false);
    root = api_add_int(root, "Queue", &opt_queue, false);
    root = api_add_int(root, "Expiry", &opt_expiry, false);
#ifdef USE_USBUTILS
    if (hotplug_time == 0)
        root = api_add_const(root, "Hotplug", DISABLED, false);
    else
        root = api_add_int(root, "Hotplug", &hotplug_time, false);
#else
    root = api_add_const(root, "Hotplug", NONE, false);
#endif

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static const char *status2str(enum alive status)
{
    switch (status)
    {
        case LIFE_WELL:
            return ALIVE;
        case LIFE_SICK:
            return SICK;
        case LIFE_DEAD:
            return DEAD;
        case LIFE_NOSTART:
            return NOSTART;
        case LIFE_INIT:
            return INIT;
        default:
            return UNKNOWN;
    }
}

#ifdef HAVE_AN_ASIC
static void ascstatus(struct io_data *io_data, int asc, bool isjson, bool precom)
{
    struct api_data *root = NULL;
    char *enabled;
    char *status;
    int numasc = numascs();

    if (numasc > 0 && asc >= 0 && asc < numasc)
    {
        int dev = ascdevice(asc);
        if (dev < 0) // Should never happen
            return;

        struct cgpu_info *cgpu = get_devices(dev);
        float temp = cgpu->temp;
        double dev_runtime;

        dev_runtime = cgpu_runtime(cgpu);

        cgpu->utility = cgpu->accepted / dev_runtime * 60;

        if (cgpu->deven != DEV_DISABLED)
            enabled = (char *)YES;
        else
            enabled = (char *)NO;

        status = (char *)status2str(cgpu->status);

        root = api_add_int(root, "ASC", &asc, false);
        root = api_add_string(root, "Name", cgpu->drv->name, false);
        root = api_add_int(root, "ID", &(cgpu->device_id), false);
        root = api_add_string(root, "Enabled", enabled, false);
        root = api_add_string(root, "Status", status, false);
        root = api_add_temp(root, "Temperature", &temp, false);
        double mhs = cgpu->total_mhashes / dev_runtime;
        root = api_add_mhs(root, "MHS av", &mhs, false);
        char mhsname[27];
        sprintf(mhsname, "MHS %ds", opt_log_interval);
        root = api_add_mhs(root, mhsname, &(cgpu->rolling), false);
        root = api_add_int(root, "Accepted", &(cgpu->accepted), false);
        root = api_add_int(root, "Rejected", &(cgpu->rejected), false);
        root = api_add_int(root, "Hardware Errors", &(cgpu->hw_errors), false);
        root = api_add_utility(root, "Utility", &(cgpu->utility), false);
        int last_share_pool = cgpu->last_share_pool_time > 0 ?
                              cgpu->last_share_pool : -1;
        root = api_add_int(root, "Last Share Pool", &last_share_pool, false);
        root = api_add_time(root, "Last Share Time", &(cgpu->last_share_pool_time), false);
        root = api_add_mhtotal(root, "Total MH", &(cgpu->total_mhashes), false);
        root = api_add_int64(root, "Diff1 Work", &(cgpu->diff1), false);
        root = api_add_diff(root, "Difficulty Accepted", &(cgpu->diff_accepted), false);
        root = api_add_diff(root, "Difficulty Rejected", &(cgpu->diff_rejected), false);
        root = api_add_diff(root, "Last Share Difficulty", &(cgpu->last_share_diff), false);
#ifdef USE_USBUTILS
        root = api_add_bool(root, "No Device", &(cgpu->usbinfo.nodev), false);
#endif
        root = api_add_time(root, "Last Valid Work", &(cgpu->last_device_valid_work), false);
        double hwp = (cgpu->hw_errors + cgpu->diff1) ?
                     (double)(cgpu->hw_errors) / (double)(cgpu->hw_errors + cgpu->diff1) : 0;
        root = api_add_percent(root, "Device Hardware%", &hwp, false);
        double rejp = cgpu->diff1 ?
                      (double)(cgpu->diff_rejected) / (double)(cgpu->diff1) : 0;
        root = api_add_percent(root, "Device Rejected%", &rejp, false);
        root = api_add_elapsed(root, "Device Elapsed", &(dev_runtime), false);

        root = print_data(io_data, root, isjson, precom);
    }
}
#endif

#ifdef HAVE_AN_FPGA
static void pgastatus(struct io_data *io_data, int pga, bool isjson, bool precom)
{
    struct api_data *root = NULL;
    char *enabled;
    char *status;
    int numpga = numpgas();

    if (numpga > 0 && pga >= 0 && pga < numpga)
    {
        int dev = pgadevice(pga);
        if (dev < 0) // Should never happen
            return;

        struct cgpu_info *cgpu = get_devices(dev);
        double frequency = 0;
        float temp = cgpu->temp;
        struct timeval now;
        double dev_runtime;

        if (cgpu->dev_start_tv.tv_sec == 0)
            dev_runtime = total_secs;
        else
        {
            cgtime(&now);
            dev_runtime = tdiff(&now, &(cgpu->dev_start_tv));
        }

        if (dev_runtime < 1.0)
            dev_runtime = 1.0;

#ifdef USE_MODMINER
        if (cgpu->drv->drv_id == DRIVER_modminer)
            frequency = cgpu->clock;
#endif

        cgpu->utility = cgpu->accepted / dev_runtime * 60;

        if (cgpu->deven != DEV_DISABLED)
            enabled = (char *)YES;
        else
            enabled = (char *)NO;

        status = (char *)status2str(cgpu->status);

        root = api_add_int(root, "PGA", &pga, false);
        root = api_add_string(root, "Name", cgpu->drv->name, false);
        root = api_add_int(root, "ID", &(cgpu->device_id), false);
        root = api_add_string(root, "Enabled", enabled, false);
        root = api_add_string(root, "Status", status, false);
        root = api_add_temp(root, "Temperature", &temp, false);
        double mhs = cgpu->total_mhashes / dev_runtime;
        root = api_add_mhs(root, "MHS av", &mhs, false);
        char mhsname[27];
        sprintf(mhsname, "MHS %ds", opt_log_interval);
        root = api_add_mhs(root, mhsname, &(cgpu->rolling), false);
        root = api_add_int(root, "Accepted", &(cgpu->accepted), false);
        root = api_add_int(root, "Rejected", &(cgpu->rejected), false);
        root = api_add_int(root, "Hardware Errors", &(cgpu->hw_errors), false);
        root = api_add_utility(root, "Utility", &(cgpu->utility), false);
        int last_share_pool = cgpu->last_share_pool_time > 0 ?
                              cgpu->last_share_pool : -1;
        root = api_add_int(root, "Last Share Pool", &last_share_pool, false);
        root = api_add_time(root, "Last Share Time", &(cgpu->last_share_pool_time), false);
        root = api_add_mhtotal(root, "Total MH", &(cgpu->total_mhashes), false);
        root = api_add_freq(root, "Frequency", &frequency, false);
        root = api_add_int64(root, "Diff1 Work", &(cgpu->diff1), false);
        root = api_add_diff(root, "Difficulty Accepted", &(cgpu->diff_accepted), false);
        root = api_add_diff(root, "Difficulty Rejected", &(cgpu->diff_rejected), false);
        root = api_add_diff(root, "Last Share Difficulty", &(cgpu->last_share_diff), false);
#ifdef USE_USBUTILS
        root = api_add_bool(root, "No Device", &(cgpu->usbinfo.nodev), false);
#endif
        root = api_add_time(root, "Last Valid Work", &(cgpu->last_device_valid_work), false);
        double hwp = (cgpu->hw_errors + cgpu->diff1) ?
                     (double)(cgpu->hw_errors) / (double)(cgpu->hw_errors + cgpu->diff1) : 0;
        root = api_add_percent(root, "Device Hardware%", &hwp, false);
        double rejp = cgpu->diff1 ?
                      (double)(cgpu->diff_rejected) / (double)(cgpu->diff1) : 0;
        root = api_add_percent(root, "Device Rejected%", &rejp, false);
        root = api_add_elapsed(root, "Device Elapsed", &(dev_runtime), false);

        root = print_data(io_data, root, isjson, precom);
    }
}
#endif

static void devstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    bool io_open = false;
    int devcount = 0;
    int numasc = 0;
    int numpga = 0;
    int i;

#ifdef HAVE_AN_ASIC
    numasc = numascs();
#endif

#ifdef HAVE_AN_FPGA
    numpga = numpgas();
#endif

    if (numpga == 0 && numasc == 0)
    {
        message(io_data, MSG_NODEVS, 0, NULL, isjson);
        return;
    }


    message(io_data, MSG_DEVS, 0, NULL, isjson);
    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_DEVS);

#ifdef HAVE_AN_ASIC
    if (numasc > 0)
    {
        for (i = 0; i < numasc; i++)
        {
            ascstatus(io_data, i, isjson, isjson && devcount > 0);

            devcount++;
        }
    }
#endif

#ifdef HAVE_AN_FPGA
    if (numpga > 0)
    {
        for (i = 0; i < numpga; i++)
        {
            pgastatus(io_data, i, isjson, isjson && devcount > 0);

            devcount++;
        }
    }
#endif

    if (isjson && io_open)
        io_close(io_data);
}

static void edevstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    bool io_open = false;
    int devcount = 0;
    int numasc = 0;
    int numpga = 0;
    int i;
#ifdef USE_USBUTILS
    time_t howoldsec = 0;
#endif

#ifdef HAVE_AN_ASIC
    numasc = numascs();
#endif

#ifdef HAVE_AN_FPGA
    numpga = numpgas();
#endif

    if (numpga == 0 && numasc == 0)
    {
        message(io_data, MSG_NODEVS, 0, NULL, isjson);
        return;
    }

#ifdef USE_USBUTILS
    if (param && *param)
        howoldsec = (time_t)atoi(param);
#endif

    message(io_data, MSG_DEVS, 0, NULL, isjson);
    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_DEVS);

#ifdef HAVE_AN_ASIC
    if (numasc > 0)
    {
        for (i = 0; i < numasc; i++)
        {
#ifdef USE_USBUTILS
            int dev = ascdevice(i);
            if (dev < 0) // Should never happen
                continue;

            struct cgpu_info *cgpu = get_devices(dev);
            if (!cgpu)
                continue;
            if (cgpu->blacklisted)
                continue;
            if (cgpu->usbinfo.nodev)
            {
                if (howoldsec <= 0)
                    continue;
                if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec)
                    continue;
            }
#endif

            ascstatus(io_data, i, isjson, isjson && devcount > 0);

            devcount++;
        }
    }
#endif

#ifdef HAVE_AN_FPGA
    if (numpga > 0)
    {
        for (i = 0; i < numpga; i++)
        {
#ifdef USE_USBUTILS
            int dev = pgadevice(i);
            if (dev < 0) // Should never happen
                continue;

            struct cgpu_info *cgpu = get_devices(dev);
            if (!cgpu)
                continue;
            if (cgpu->blacklisted)
                continue;
            if (cgpu->usbinfo.nodev)
            {
                if (howoldsec <= 0)
                    continue;
                if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec)
                    continue;
            }
#endif

            pgastatus(io_data, i, isjson, isjson && devcount > 0);

            devcount++;
        }
    }
#endif

    if (isjson && io_open)
        io_close(io_data);
}

#ifdef HAVE_AN_FPGA
static void pgadev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    bool io_open = false;
    int numpga = numpgas();
    int id;

    if (numpga == 0)
    {
        message(io_data, MSG_PGANON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numpga)
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    message(io_data, MSG_PGADEV, id, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_PGA);

    pgastatus(io_data, id, isjson, false);

    if (isjson && io_open)
        io_close(io_data);
}

static void pgaenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    int numpga = numpgas();
    struct thr_info *thr;
    int pga;
    int id;
    int i;

    if (numpga == 0)
    {
        message(io_data, MSG_PGANON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numpga)
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    int dev = pgadevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);

    applog(LOG_DEBUG, "API: request to pgaenable pgaid %d device %d %s%u",
           id, dev, cgpu->drv->name, cgpu->device_id);

    if (cgpu->deven != DEV_DISABLED)
    {
        message(io_data, MSG_PGALRENA, id, NULL, isjson);
        return;
    }

#if 0 /* A DISABLED device wont change status FIXME: should disabling make it WELL? */
    if (cgpu->status != LIFE_WELL)
    {
        message(io_data, MSG_PGAUNW, id, NULL, isjson);
        return;
    }
#endif

#ifdef USE_USBUTILS
    if (cgpu->usbinfo.nodev)
    {
        message(io_data, MSG_PGAUSBNODEV, id, NULL, isjson);
        return;
    }
#endif

    for (i = 0; i < mining_threads; i++)
    {
        thr = get_thread(i);
        pga = thr->cgpu->cgminer_id;
        if (pga == dev)
        {
            cgpu->deven = DEV_ENABLED;
            applog(LOG_DEBUG, "API: Pushing sem post to thread %d", thr->id);
            cgsem_post(&thr->sem);
        }
    }

    message(io_data, MSG_PGAENA, id, NULL, isjson);
}

static void pgadisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    int numpga = numpgas();
    int id;

    if (numpga == 0)
    {
        message(io_data, MSG_PGANON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numpga)
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    int dev = pgadevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);

    applog(LOG_DEBUG, "API: request to pgadisable pgaid %d device %d %s%u",
           id, dev, cgpu->drv->name, cgpu->device_id);

    if (cgpu->deven == DEV_DISABLED)
    {
        message(io_data, MSG_PGALRDIS, id, NULL, isjson);
        return;
    }

    cgpu->deven = DEV_DISABLED;

    message(io_data, MSG_PGADIS, id, NULL, isjson);
}

static void pgaidentify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    struct device_drv *drv;
    int numpga = numpgas();
    int id;

    if (numpga == 0)
    {
        message(io_data, MSG_PGANON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numpga)
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    int dev = pgadevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);
    drv = cgpu->drv;

    if (!drv->identify_device)
        message(io_data, MSG_PGANOID, id, NULL, isjson);
    else
    {
        drv->identify_device(cgpu);
        message(io_data, MSG_PGAIDENT, id, NULL, isjson);
    }
}
#endif
static void poolstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open = false;
    char *status, *lp;
    int i;
    int hour = 0;
    int minute = 0;
    int second = 0;

    char lasttime[256] = {0};
    long timediff = 0;

    if (total_pools == 0)
    {
        message(io_data, MSG_NOPOOL, 0, NULL, isjson);
        return;
    }

    message(io_data, MSG_POOL, 0, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_POOLS);

    for (i = 0; i < total_pools; i++)
    {
        struct pool *pool = pools[i];

        if (pool->removed)
            continue;

        switch (pool->enabled)
        {
            case POOL_DISABLED:
                status = (char *)DISABLED;
                break;
            case POOL_REJECTING:
                status = (char *)REJECTING;
                break;
            case POOL_ENABLED:
                if (pool->idle)
                    status = (char *)DEAD;
                else
                    status = (char *)ALIVE;
                break;
            default:
                status = (char *)UNKNOWN;
                break;
        }

        if (pool->hdr_path)
            lp = (char *)YES;
        else
            lp = (char *)NO;

        if(pool->last_share_time <= 0)
        {
            strcpy(lasttime, "0");
        }
        else
        {
            timediff = time(NULL) - pool->last_share_time;
            if(timediff < 0)
                timediff = 0;

            hour = timediff / 3600;
            minute = (timediff % 3600) / 60;
            second = (timediff % 3600) % 60;
            sprintf(lasttime, "%d:%02d:%02d", hour, minute, second);
        }

        root = api_add_int(root, "POOL", &i, false);
        root = api_add_escape(root, "URL", pool->rpc_url, false);
        root = api_add_string(root, "Status", status, false);
        root = api_add_int(root, "Priority", &(pool->prio), false);
        root = api_add_int(root, "Quota", &pool->quota, false);
        root = api_add_string(root, "Long Poll", lp, false);
        root = api_add_uint(root, "Getworks", &(pool->getwork_requested), false);
        root = api_add_int64(root, "Accepted", &(pool->accepted), false);
        root = api_add_int64(root, "Rejected", &(pool->rejected), false);
        //root = api_add_int(root, "Works", &pool->works, false);
        root = api_add_uint(root, "Discarded", &(pool->discarded_work), false);
        root = api_add_uint(root, "Stale", &(pool->stale_shares), false);
        root = api_add_uint(root, "Get Failures", &(pool->getfail_occasions), false);
        root = api_add_uint(root, "Remote Failures", &(pool->remotefail_occasions), false);
        root = api_add_escape(root, "User", pool->rpc_user, false);
        //root = api_add_time(root, "Last Share Time", &(pool->last_share_time), false);
        root = api_add_string(root, "Last Share Time", lasttime, false);
        root = api_add_string(root, "Diff", pool->diff, false);
        root = api_add_int64(root, "Diff1 Shares", &(pool->diff1), false);
        if (pool->rpc_proxy)
        {
            root = api_add_const(root, "Proxy Type", proxytype(pool->rpc_proxytype), false);
            root = api_add_escape(root, "Proxy", pool->rpc_proxy, false);
        }
        else
        {
            root = api_add_const(root, "Proxy Type", BLANK, false);
            root = api_add_const(root, "Proxy", BLANK, false);
        }
        root = api_add_diff(root, "Difficulty Accepted", &(pool->diff_accepted), false);
        root = api_add_diff(root, "Difficulty Rejected", &(pool->diff_rejected), false);
        root = api_add_diff(root, "Difficulty Stale", &(pool->diff_stale), false);
        root = api_add_diff(root, "Last Share Difficulty", &(pool->last_share_diff), false);
        root = api_add_bool(root, "Has Stratum", &(pool->has_stratum), false);
        root = api_add_bool(root, "Stratum Active", &(pool->stratum_active), false);
        if (pool->stratum_active)
            root = api_add_escape(root, "Stratum URL", pool->stratum_url, false);
        else
            root = api_add_const(root, "Stratum URL", BLANK, false);
        root = api_add_bool(root, "Has GBT", &(pool->has_gbt), false);
        root = api_add_uint64(root, "Best Share", &(pool->best_diff), true);
        double rejp = (pool->diff_accepted + pool->diff_rejected + pool->diff_stale) ?
                      (double)(pool->diff_rejected) / (double)(pool->diff_accepted + pool->diff_rejected + pool->diff_stale) : 0;
        root = api_add_percent(root, "Pool Rejected%", &rejp, false);
        double stalep = (pool->diff_accepted + pool->diff_rejected + pool->diff_stale) ?
                        (double)(pool->diff_stale) / (double)(pool->diff_accepted + pool->diff_rejected + pool->diff_stale) : 0;
        root = api_add_percent(root, "Pool Stale%", &stalep, false);

        root = print_data(io_data, root, isjson, isjson && (i > 0));
    }

    if (isjson && io_open)
        io_close(io_data);
}

static double getAVGhashrate()
{
    return (total_mhashes_done - new_total_mhashes_done)/ 1000 / (total_secs - new_total_secs);
}

static void lcddisplay(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open = false;
    char *status, *lp;
    double ghs;

    char szindex[32] = {0};
    char szfan[32] = {0};
    char sztemp[32] = {0};
    char szpool[32] = {0};
    char szuser[32] = {0};

    struct pool *pool = current_pool();

    message(io_data, MSG_POOL, 0, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_POOLS);

    ghs = getAVGhashrate();

    strcpy(szindex, "0");
    root = api_add_string(root, "LCD", szindex, false);

    root = api_add_mhs(root, "GHS5s", &(g_displayed_rolling), false);
    root = api_add_mhs(root, "GHSavg", &(ghs), false);

    sprintf(szfan, "%d", g_max_fan);
    root = api_add_string(root, "fan", szfan, false);
    sprintf(sztemp, "%d", g_max_temp);
    root = api_add_string(root, "temp", sztemp, false);

    if(pool == NULL)
    {
        strcpy(szpool, "no");
        strcpy(szuser, "no");
        root = api_add_string(root, "pool", szpool, false);
        root = api_add_string(root, "user", szuser, false);
    }
    else
    {
        root = api_add_string(root, "pool", pool->rpc_url, false);
        root = api_add_string(root, "user", pool->rpc_user, false);
    }

    root = print_data(io_data, root, isjson, isjson);

    if (isjson && io_open)
        io_close(io_data);
}

static void summary(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;
    double utility, ghs, work_utility;

    message(io_data, MSG_SUMM, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_SUMMARY : _SUMMARY COMSTR);

    // stop hashmeter() changing some while copying
    mutex_lock(&hash_lock);
#ifdef USE_BITMAIN_C5
    total_diff1 = total_diff_accepted + total_diff_rejected + total_diff_stale;
#endif

    utility = total_accepted / ( total_secs ? total_secs : 1 ) * 60;

    ghs = getAVGhashrate();

    work_utility = total_diff1 / ( total_secs ? total_secs : 1 ) * 60;

    root = api_add_elapsed(root, "Elapsed", &(total_secs), true);
#ifndef USE_BITMAIN_C5
    root = api_add_mhs(root, "GHS 5s", &(g_displayed_rolling), false);
#else
    root = api_add_string(root, "GHS 5s", displayed_hash_rate, false);
#endif
    root = api_add_mhs(root, "GHS av", &(ghs), false);
    root = api_add_uint(root, "Found Blocks", &(found_blocks), true);
    root = api_add_int64(root, "Getworks", &(total_getworks), true);
    root = api_add_int64(root, "Accepted", &(total_accepted), true);
    root = api_add_int64(root, "Rejected", &(total_rejected), true);
    root = api_add_int(root, "Hardware Errors", &(hw_errors), true);
    root = api_add_utility(root, "Utility", &(utility), false);
    root = api_add_int64(root, "Discarded", &(total_discarded), true);
    root = api_add_int64(root, "Stale", &(total_stale), true);
    root = api_add_uint(root, "Get Failures", &(total_go), true);
    root = api_add_uint(root, "Local Work", &(local_work), true);
    root = api_add_uint(root, "Remote Failures", &(total_ro), true);
    root = api_add_uint(root, "Network Blocks", &(new_blocks), true);
    root = api_add_mhtotal(root, "Total MH", &(total_mhashes_done), true);
    root = api_add_utility(root, "Work Utility", &(work_utility), false);
    root = api_add_diff(root, "Difficulty Accepted", &(total_diff_accepted), true);
    root = api_add_diff(root, "Difficulty Rejected", &(total_diff_rejected), true);
    root = api_add_diff(root, "Difficulty Stale", &(total_diff_stale), true);
    root = api_add_uint64(root, "Best Share", &(best_diff), true);
    double hwp = (hw_errors + total_diff1) ?
                 (double)(hw_errors) / (double)(hw_errors + total_diff1) : 0;
    root = api_add_percent(root, "Device Hardware%", &hwp, false);
    double rejp = total_diff1 ?
                  (double)(total_diff_rejected) / (double)(total_diff1) : 0;
    root = api_add_percent(root, "Device Rejected%", &rejp, false);
    double prejp = (total_diff_accepted + total_diff_rejected + total_diff_stale) ?
                   (double)(total_diff_rejected) / (double)(total_diff_accepted + total_diff_rejected + total_diff_stale) : 0;
    root = api_add_percent(root, "Pool Rejected%", &prejp, false);
    double stalep = (total_diff_accepted + total_diff_rejected + total_diff_stale) ?
                    (double)(total_diff_stale) / (double)(total_diff_accepted + total_diff_rejected + total_diff_stale) : 0;
    root = api_add_percent(root, "Pool Stale%", &stalep, false);
    root = api_add_time(root, "Last getwork", &last_getwork, false);

    mutex_unlock(&hash_lock);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static void noncenum(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;

    message(io_data, MSG_NONCE_NUM, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_NONCENUM : _NONCENUM COMSTR);

    root = api_add_string(root, "10min nonce",nonce_num10_string, false);
    root = api_add_string(root, "30min nonce",nonce_num30_string , false);
    root = api_add_string(root, "60min nonce",nonce_num60_string , false);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}


static void pgacount(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;
    int count = 0;

#ifdef HAVE_AN_FPGA
    count = numpgas();
#endif

    message(io_data, MSG_NUMPGA, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_PGAS : _PGAS COMSTR);

    root = api_add_int(root, "Count", &count, false);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static void switchpool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct pool *pool;
    int id;

    if (total_pools == 0)
    {
        message(io_data, MSG_NOPOOL, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISPID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    cg_rlock(&control_lock);
    if (id < 0 || id >= total_pools)
    {
        cg_runlock(&control_lock);
        message(io_data, MSG_INVPID, id, NULL, isjson);
        return;
    }

    pool = pools[id];
    pool->enabled = POOL_ENABLED;
    cg_runlock(&control_lock);
    switch_pools(pool);

    message(io_data, MSG_SWITCHP, id, NULL, isjson);
}

static void copyadvanceafter(char ch, char **param, char **buf)
{
#define src_p (*param)
#define dst_b (*buf)

    while (*src_p && *src_p != ch)
    {
        if (*src_p == '\\' && *(src_p+1) != '\0')
            src_p++;

        *(dst_b++) = *(src_p++);
    }
    if (*src_p)
        src_p++;

    *(dst_b++) = '\0';
}

static bool pooldetails(char *param, char **url, char **user, char **pass)
{
    char *ptr, *buf;

    ptr = buf = malloc(strlen(param)+1);
    if (unlikely(!buf))
        quit(1, "Failed to malloc pooldetails buf");

    *url = buf;

    // copy url
    copyadvanceafter(',', &param, &buf);

    if (!(*param)) // missing user
        goto exitsama;

    *user = buf;

    // copy user
    copyadvanceafter(',', &param, &buf);

    if (!*param) // missing pass
        goto exitsama;

    *pass = buf;

    // copy pass
    copyadvanceafter(',', &param, &buf);

    return true;

exitsama:
    free(ptr);
    return false;
}

static void addpool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    char *url, *user, *pass;
    struct pool *pool;
    char *ptr;

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISPDP, 0, NULL, isjson);
        return;
    }

    if (!pooldetails(param, &url, &user, &pass))
    {
        ptr = escape_string(param, isjson);
        message(io_data, MSG_INVPDP, 0, ptr, isjson);
        if (ptr != param)
            free(ptr);
        ptr = NULL;
        return;
    }

    pool = add_pool();
    detect_stratum(pool, url);
    add_pool_details(pool, true, url, user, pass);

    ptr = escape_string(url, isjson);
    message(io_data, MSG_ADDPOOL, pool->pool_no, ptr, isjson);
    if (ptr != url)
        free(ptr);
    ptr = NULL;
}

static void enablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct pool *pool;
    int id;

    if (total_pools == 0)
    {
        message(io_data, MSG_NOPOOL, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISPID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= total_pools)
    {
        message(io_data, MSG_INVPID, id, NULL, isjson);
        return;
    }

    pool = pools[id];
    if (pool->enabled == POOL_ENABLED)
    {
        message(io_data, MSG_ALRENAP, id, NULL, isjson);
        return;
    }

    pool->enabled = POOL_ENABLED;
    if (pool->prio < current_pool()->prio)
        switch_pools(pool);

    message(io_data, MSG_ENAPOOL, id, NULL, isjson);
}

static void poolpriority(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    char *ptr, *next;
    int i, pr, prio = 0;

    // TODO: all cgminer code needs a mutex added everywhere for change
    //  access to total_pools and also parts of the pools[] array,
    //  just copying total_pools here wont solve that

    if (total_pools == 0)
    {
        message(io_data, MSG_NOPOOL, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISPID, 0, NULL, isjson);
        return;
    }

    bool pools_changed[total_pools];
    int new_prio[total_pools];
    for (i = 0; i < total_pools; ++i)
        pools_changed[i] = false;

    next = param;
    while (next && *next)
    {
        ptr = next;
        next = strchr(ptr, ',');
        if (next)
            *(next++) = '\0';

        i = atoi(ptr);
        if (i < 0 || i >= total_pools)
        {
            message(io_data, MSG_INVPID, i, NULL, isjson);
            return;
        }

        if (pools_changed[i])
        {
            message(io_data, MSG_DUPPID, i, NULL, isjson);
            return;
        }

        pools_changed[i] = true;
        new_prio[i] = prio++;
    }

    // Only change them if no errors
    for (i = 0; i < total_pools; i++)
    {
        if (pools_changed[i])
            pools[i]->prio = new_prio[i];
    }

    // In priority order, cycle through the unchanged pools and append them
    for (pr = 0; pr < total_pools; pr++)
        for (i = 0; i < total_pools; i++)
        {
            if (!pools_changed[i] && pools[i]->prio == pr)
            {
                pools[i]->prio = prio++;
                pools_changed[i] = true;
                break;
            }
        }

    if (current_pool()->prio)
        switch_pools(NULL);

    message(io_data, MSG_POOLPRIO, 0, NULL, isjson);
}

static void poolquota(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct pool *pool;
    int quota, id;
    char *comma;

    if (total_pools == 0)
    {
        message(io_data, MSG_NOPOOL, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISPID, 0, NULL, isjson);
        return;
    }

    comma = strchr(param, ',');
    if (!comma)
    {
        message(io_data, MSG_CONVAL, 0, param, isjson);
        return;
    }

    *(comma++) = '\0';

    id = atoi(param);
    if (id < 0 || id >= total_pools)
    {
        message(io_data, MSG_INVPID, id, NULL, isjson);
        return;
    }
    pool = pools[id];

    quota = atoi(comma);
    if (quota < 0)
    {
        message(io_data, MSG_INVNEG, quota, pool->rpc_url, isjson);
        return;
    }

    pool->quota = quota;
    adjust_quota_gcd();
    message(io_data, MSG_SETQUOTA, quota, pool->rpc_url, isjson);
}

static void disablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct pool *pool;
    int id;

    if (total_pools == 0)
    {
        message(io_data, MSG_NOPOOL, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISPID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= total_pools)
    {
        message(io_data, MSG_INVPID, id, NULL, isjson);
        return;
    }

    pool = pools[id];
    if (pool->enabled == POOL_DISABLED)
    {
        message(io_data, MSG_ALRDISP, id, NULL, isjson);
        return;
    }

    if (enabled_pools <= 1)
    {
        message(io_data, MSG_DISLASTP, id, NULL, isjson);
        return;
    }

    pool->enabled = POOL_DISABLED;
    if (pool == current_pool())
        switch_pools(NULL);

    message(io_data, MSG_DISPOOL, id, NULL, isjson);
}

static void removepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct pool *pool;
    char *rpc_url;
    bool dofree = false;
    int id;

    if (total_pools == 0)
    {
        message(io_data, MSG_NOPOOL, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISPID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= total_pools)
    {
        message(io_data, MSG_INVPID, id, NULL, isjson);
        return;
    }

    if (total_pools <= 1)
    {
        message(io_data, MSG_REMLASTP, id, NULL, isjson);
        return;
    }

    pool = pools[id];
    if (pool == current_pool())
        switch_pools(NULL);

    if (pool == current_pool())
    {
        message(io_data, MSG_ACTPOOL, id, NULL, isjson);
        return;
    }

    pool->enabled = POOL_DISABLED;
    rpc_url = escape_string(pool->rpc_url, isjson);
    if (rpc_url != pool->rpc_url)
        dofree = true;

    remove_pool(pool);

    message(io_data, MSG_REMPOOL, id, rpc_url, isjson);

    if (dofree)
        free(rpc_url);
    rpc_url = NULL;
}

void doquit(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    if (isjson)
        io_put(io_data, JSON_ACTION JSON_BYE);
    else
        io_put(io_data, _BYE);

    bye = true;
    do_a_quit = true;
}

void dorestart(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    if (isjson)
        io_put(io_data, JSON_ACTION JSON_RESTART);
    else
        io_put(io_data, _RESTART);

    bye = true;
    do_a_restart = true;
}

void privileged(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    message(io_data, MSG_ACCOK, 0, NULL, isjson);
}

void notifystatus(struct io_data *io_data, int device, struct cgpu_info *cgpu, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    char *reason;

    if (cgpu->device_last_not_well == 0)
        reason = REASON_NONE;
    else
        switch(cgpu->device_not_well_reason)
        {
            case REASON_THREAD_FAIL_INIT:
                reason = REASON_THREAD_FAIL_INIT_STR;
                break;
            case REASON_THREAD_ZERO_HASH:
                reason = REASON_THREAD_ZERO_HASH_STR;
                break;
            case REASON_THREAD_FAIL_QUEUE:
                reason = REASON_THREAD_FAIL_QUEUE_STR;
                break;
            case REASON_DEV_SICK_IDLE_60:
                reason = REASON_DEV_SICK_IDLE_60_STR;
                break;
            case REASON_DEV_DEAD_IDLE_600:
                reason = REASON_DEV_DEAD_IDLE_600_STR;
                break;
            case REASON_DEV_NOSTART:
                reason = REASON_DEV_NOSTART_STR;
                break;
            case REASON_DEV_OVER_HEAT:
                reason = REASON_DEV_OVER_HEAT_STR;
                break;
            case REASON_DEV_THERMAL_CUTOFF:
                reason = REASON_DEV_THERMAL_CUTOFF_STR;
                break;
            case REASON_DEV_COMMS_ERROR:
                reason = REASON_DEV_COMMS_ERROR_STR;
                break;
            default:
                reason = REASON_UNKNOWN_STR;
                break;
        }

    // ALL counters (and only counters) must start the name with a '*'
    // Simplifies future external support for identifying new counters
    root = api_add_int(root, "NOTIFY", &device, false);
    root = api_add_string(root, "Name", cgpu->drv->name, false);
    root = api_add_int(root, "ID", &(cgpu->device_id), false);
    root = api_add_time(root, "Last Well", &(cgpu->device_last_well), false);
    root = api_add_time(root, "Last Not Well", &(cgpu->device_last_not_well), false);
    root = api_add_string(root, "Reason Not Well", reason, false);
    root = api_add_int(root, "*Thread Fail Init", &(cgpu->thread_fail_init_count), false);
    root = api_add_int(root, "*Thread Zero Hash", &(cgpu->thread_zero_hash_count), false);
    root = api_add_int(root, "*Thread Fail Queue", &(cgpu->thread_fail_queue_count), false);
    root = api_add_int(root, "*Dev Sick Idle 60s", &(cgpu->dev_sick_idle_60_count), false);
    root = api_add_int(root, "*Dev Dead Idle 600s", &(cgpu->dev_dead_idle_600_count), false);
    root = api_add_int(root, "*Dev Nostart", &(cgpu->dev_nostart_count), false);
    root = api_add_int(root, "*Dev Over Heat", &(cgpu->dev_over_heat_count), false);
    root = api_add_int(root, "*Dev Thermal Cutoff", &(cgpu->dev_thermal_cutoff_count), false);
    root = api_add_int(root, "*Dev Comms Error", &(cgpu->dev_comms_error_count), false);
    root = api_add_int(root, "*Dev Throttle", &(cgpu->dev_throttle_count), false);

    root = print_data(io_data, root, isjson, isjson && (device > 0));
}

static void notify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, char group)
{
    struct cgpu_info *cgpu;
    bool io_open = false;
    int i;

    if (total_devices == 0)
    {
        message(io_data, MSG_NODEVS, 0, NULL, isjson);
        return;
    }

    message(io_data, MSG_NOTIFY, 0, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_NOTIFY);

    for (i = 0; i < total_devices; i++)
    {
        cgpu = get_devices(i);
        notifystatus(io_data, i, cgpu, isjson, group);
    }

    if (isjson && io_open)
        io_close(io_data);
}

static void devdetails(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open = false;
    struct cgpu_info *cgpu;
    int i;

    if (total_devices == 0)
    {
        message(io_data, MSG_NODEVS, 0, NULL, isjson);
        return;
    }

    message(io_data, MSG_DEVDETAILS, 0, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_DEVDETAILS);

    for (i = 0; i < total_devices; i++)
    {
        cgpu = get_devices(i);

        root = api_add_int(root, "DEVDETAILS", &i, false);
        root = api_add_string(root, "Name", cgpu->drv->name, false);
        root = api_add_int(root, "ID", &(cgpu->device_id), false);
        root = api_add_string(root, "Driver", cgpu->drv->dname, false);
        root = api_add_const(root, "Kernel", cgpu->kname ? : BLANK, false);
        root = api_add_const(root, "Model", cgpu->name ? : BLANK, false);
        root = api_add_const(root, "Device Path", cgpu->device_path ? : BLANK, false);

        root = print_data(io_data, root, isjson, isjson && (i > 0));
    }

    if (isjson && io_open)
        io_close(io_data);
}

void dosave(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    char filename[PATH_MAX];
    FILE *fcfg;
    char *ptr;

    if (param == NULL || *param == '\0')
    {
        default_save_file(filename);
        param = filename;
    }

    fcfg = fopen(param, "w");
    if (!fcfg)
    {
        ptr = escape_string(param, isjson);
        message(io_data, MSG_BADFN, 0, ptr, isjson);
        if (ptr != param)
            free(ptr);
        ptr = NULL;
        return;
    }

    write_config(fcfg);
    fclose(fcfg);

    ptr = escape_string(param, isjson);
    message(io_data, MSG_SAVED, 0, ptr, isjson);
    if (ptr != param)
        free(ptr);
    ptr = NULL;
}

static int itemstats(struct io_data *io_data, int i, char *id, struct cgminer_stats *stats, struct cgminer_pool_stats *pool_stats, struct api_data *extra, struct cgpu_info *cgpu, bool isjson)
{
    struct api_data *root = NULL;
    double ghs;


    ghs = getAVGhashrate();

    root = api_add_int(root, "STATS", &i, false);
    root = api_add_string(root, "ID", id, false);
    root = api_add_elapsed(root, "Elapsed", &(total_secs), false);
    root = api_add_uint32(root, "Calls", &(stats->getwork_calls), false);
    root = api_add_timeval(root, "Wait", &(stats->getwork_wait), false);
    root = api_add_timeval(root, "Max", &(stats->getwork_wait_max), false);
    root = api_add_timeval(root, "Min", &(stats->getwork_wait_min), false);
#ifndef USE_BITMAIN_C5
    root = api_add_mhs(root, "GHS 5s", &(g_displayed_rolling), false);
#else
    root = api_add_string(root, "GHS 5s", displayed_hash_rate, false);
#endif

    root = api_add_mhs(root, "GHS av", &(ghs), false);

    /*
    if (pool_stats) {
        root = api_add_uint32(root, "Pool Calls", &(pool_stats->getwork_calls), false);
        root = api_add_uint32(root, "Pool Attempts", &(pool_stats->getwork_attempts), false);
        root = api_add_timeval(root, "Pool Wait", &(pool_stats->getwork_wait), false);
        root = api_add_timeval(root, "Pool Max", &(pool_stats->getwork_wait_max), false);
        root = api_add_timeval(root, "Pool Min", &(pool_stats->getwork_wait_min), false);
        root = api_add_double(root, "Pool Av", &(pool_stats->getwork_wait_rolling), false);
        root = api_add_bool(root, "Work Had Roll Time", &(pool_stats->hadrolltime), false);
        root = api_add_bool(root, "Work Can Roll", &(pool_stats->canroll), false);
        root = api_add_bool(root, "Work Had Expire", &(pool_stats->hadexpire), false);
        root = api_add_uint32(root, "Work Roll Time", &(pool_stats->rolltime), false);
        root = api_add_diff(root, "Work Diff", &(pool_stats->last_diff), false);
        root = api_add_diff(root, "Min Diff", &(pool_stats->min_diff), false);
        root = api_add_diff(root, "Max Diff", &(pool_stats->max_diff), false);
        root = api_add_uint32(root, "Min Diff Count", &(pool_stats->min_diff_count), false);
        root = api_add_uint32(root, "Max Diff Count", &(pool_stats->max_diff_count), false);
        root = api_add_uint64(root, "Times Sent", &(pool_stats->times_sent), false);
        root = api_add_uint64(root, "Bytes Sent", &(pool_stats->bytes_sent), false);
        root = api_add_uint64(root, "Times Recv", &(pool_stats->times_received), false);
        root = api_add_uint64(root, "Bytes Recv", &(pool_stats->bytes_received), false);
        root = api_add_uint64(root, "Net Bytes Sent", &(pool_stats->net_bytes_sent), false);
        root = api_add_uint64(root, "Net Bytes Recv", &(pool_stats->net_bytes_received), false);
    }*/

    if (extra)
        root = api_add_extra(root, extra);

    if (cgpu)
    {
#ifdef USE_USBUTILS
        char details[256];

        if (cgpu->usbinfo.pipe_count)
            snprintf(details, sizeof(details),
                     "%"PRIu64" %"PRIu64"/%"PRIu64"/%"PRIu64" %lu",
                     cgpu->usbinfo.pipe_count,
                     cgpu->usbinfo.clear_err_count,
                     cgpu->usbinfo.retry_err_count,
                     cgpu->usbinfo.clear_fail_count,
                     (unsigned long)(cgpu->usbinfo.last_pipe));
        else
            strcpy(details, "0");

        root = api_add_string(root, "USB Pipe", details, true);

        /*
        snprintf(details, sizeof(details),
             "r%"PRIu64" %.6f w%"PRIu64" %.6f",
             cgpu->usbinfo.read_delay_count,
             cgpu->usbinfo.total_read_delay,
             cgpu->usbinfo.write_delay_count,
             cgpu->usbinfo.total_write_delay);

        root = api_add_string(root, "USB Delay", details, true);

        if (cgpu->usbinfo.usb_tmo[0].count == 0 &&
            cgpu->usbinfo.usb_tmo[1].count == 0 &&
            cgpu->usbinfo.usb_tmo[2].count == 0) {
                snprintf(details, sizeof(details),
                     "%"PRIu64" 0", cgpu->usbinfo.tmo_count);
        } else {
            snprintf(details, sizeof(details),
                 "%"PRIu64" %d=%d/%d/%d/%"PRIu64"/%"PRIu64
                 " %d=%d/%d/%d/%"PRIu64"/%"PRIu64
                 " %d=%d/%d/%d/%"PRIu64"/%"PRIu64" ",
                 cgpu->usbinfo.tmo_count,
                 USB_TMO_0, cgpu->usbinfo.usb_tmo[0].count,
                 cgpu->usbinfo.usb_tmo[0].min_tmo,
                 cgpu->usbinfo.usb_tmo[0].max_tmo,
                 cgpu->usbinfo.usb_tmo[0].total_over,
                 cgpu->usbinfo.usb_tmo[0].total_tmo,
                 USB_TMO_1, cgpu->usbinfo.usb_tmo[1].count,
                 cgpu->usbinfo.usb_tmo[1].min_tmo,
                 cgpu->usbinfo.usb_tmo[1].max_tmo,
                 cgpu->usbinfo.usb_tmo[1].total_over,
                 cgpu->usbinfo.usb_tmo[1].total_tmo,
                 USB_TMO_2, cgpu->usbinfo.usb_tmo[2].count,
                 cgpu->usbinfo.usb_tmo[2].min_tmo,
                 cgpu->usbinfo.usb_tmo[2].max_tmo,
                 cgpu->usbinfo.usb_tmo[2].total_over,
                 cgpu->usbinfo.usb_tmo[2].total_tmo);
        }

        root = api_add_string(root, "USB tmo", details, true);*/
#endif
    }

    root = print_data(io_data, root, isjson, isjson && (i > 0));

    return ++i;
}

static void minerstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    struct cgpu_info *cgpu;
    bool io_open = false;
    struct api_data *extra;
    char id[20];
    int i, j;

    message(io_data, MSG_MINESTATS, 0, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_MINESTATS);

    root = api_add_string(root, "BMMiner", VERSION, false);
    root = api_add_string(root, "Miner", g_miner_version, false);
    root = api_add_string(root, "CompileTime", g_miner_compiletime, false);
    root = api_add_string(root, "Type", g_miner_type, false);
    root = print_data(io_data, root, isjson, false);

    i = 0;
    for (j = 0; j < total_devices; j++)
    {
        cgpu = get_devices(j);

        if (cgpu && cgpu->drv)
        {
            if (cgpu->drv->get_api_stats)
                extra = cgpu->drv->get_api_stats(cgpu);
            else
                extra = NULL;

            sprintf(id, "%s%d", cgpu->drv->name, cgpu->device_id);
            i = itemstats(io_data, i, id, &(cgpu->cgminer_stats), NULL, extra, cgpu, isjson);
        }
    }

    if (isjson && io_open)
        io_close(io_data);
}

static void minerestats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    bool io_open = false;
    struct api_data *extra;
    char id[20];
    int i, j;
#ifdef USE_USBUTILS
    time_t howoldsec = 0;

    if (param && *param)
        howoldsec = (time_t)atoi(param);
#endif

    message(io_data, MSG_MINESTATS, 0, NULL, isjson);
    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_MINESTATS);

    i = 0;
    for (j = 0; j < total_devices; j++)
    {
        cgpu = get_devices(j);
        if (!cgpu)
            continue;
#ifdef USE_USBUTILS
        if (cgpu->blacklisted)
            continue;
        if (cgpu->usbinfo.nodev)
        {
            if (howoldsec <= 0)
                continue;
            if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec)
                continue;
        }
#endif
        if (cgpu->drv)
        {
            if (cgpu->drv->get_api_stats)
                extra = cgpu->drv->get_api_stats(cgpu);
            else
                extra = NULL;

            sprintf(id, "%s%d", cgpu->drv->name, cgpu->device_id);
            i = itemstats(io_data, i, id, &(cgpu->cgminer_stats), NULL, extra, cgpu, isjson);
        }
    }

    if (isjson && io_open)
        io_close(io_data);
}

static void failoveronly(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISBOOL, 0, NULL, isjson);
        return;
    }

    *param = tolower(*param);

    if (*param != 't' && *param != 'f')
    {
        message(io_data, MSG_INVBOOL, 0, NULL, isjson);
        return;
    }

    bool tf = (*param == 't');

    opt_fail_only = tf;

    message(io_data, MSG_FOO, tf, NULL, isjson);
}

static void minecoin(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;

    message(io_data, MSG_MINECOIN, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_MINECOIN : _MINECOIN COMSTR);

    root = api_add_const(root, "Hash Method", SHA256STR, false);

    cg_rlock(&ch_lock);
    root = api_add_timeval(root, "Current Block Time", &block_timeval, true);
    root = api_add_string(root, "Current Block Hash", current_hash, true);
    cg_runlock(&ch_lock);

    root = api_add_bool(root, "LP", &have_longpoll, false);
    root = api_add_diff(root, "Network Difficulty", &current_diff, true);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static void debugstate(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;

    if (param == NULL)
        param = (char *)BLANK;
    else
        *param = tolower(*param);

    switch(*param)
    {
        case 's':
            opt_realquiet = true;
            break;
        case 'q':
            opt_quiet ^= true;
            break;
        case 'v':
            opt_log_output ^= true;
            if (opt_log_output)
                opt_quiet = false;
            break;
        case 'd':
            opt_debug ^= true;
            opt_log_output = opt_debug;
            if (opt_debug)
                opt_quiet = false;
            break;
        case 'r':
            opt_protocol ^= true;
            if (opt_protocol)
                opt_quiet = false;
            break;
        case 'p':
            want_per_device_stats ^= true;
            opt_log_output = want_per_device_stats;
            break;
        case 'n':
            opt_log_output = false;
            opt_debug = false;
            opt_quiet = false;
            opt_protocol = false;
            want_per_device_stats = false;
            opt_worktime = false;
            break;
        case 'w':
            opt_worktime ^= true;
            break;
#ifdef _MEMORY_DEBUG
        case 'y':
            cgmemspeedup();
            break;
        case 'z':
            cgmemrpt();
            break;
#endif
        default:
            // anything else just reports the settings
            break;
    }

    message(io_data, MSG_DEBUGSET, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_DEBUGSET : _DEBUGSET COMSTR);

    root = api_add_bool(root, "Silent", &opt_realquiet, false);
    root = api_add_bool(root, "Quiet", &opt_quiet, false);
    root = api_add_bool(root, "Verbose", &opt_log_output, false);
    root = api_add_bool(root, "Debug", &opt_debug, false);
    root = api_add_bool(root, "RPCProto", &opt_protocol, false);
    root = api_add_bool(root, "PerDevice", &want_per_device_stats, false);
    root = api_add_bool(root, "WorkTime", &opt_worktime, false);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static void setconfig(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    char *comma;
    int value;

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_CONPAR, 0, NULL, isjson);
        return;
    }

    comma = strchr(param, ',');
    if (!comma)
    {
        message(io_data, MSG_CONVAL, 0, param, isjson);
        return;
    }

    *(comma++) = '\0';
    value = atoi(comma);
    if (value < 0 || value > 9999)
    {
        message(io_data, MSG_INVNUM, value, param, isjson);
        return;
    }

    if (strcasecmp(param, "queue") == 0)
        opt_queue = value;
    else if (strcasecmp(param, "scantime") == 0)
        opt_scantime = value;
    else if (strcasecmp(param, "expiry") == 0)
        opt_expiry = value;
    else
    {
        message(io_data, MSG_UNKCON, 0, param, isjson);
        return;
    }

    message(io_data, MSG_SETCONFIG, value, param, isjson);
}

static void usbstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;

#ifdef USE_USBUTILS
    bool io_open = false;
    int count = 0;

    root = api_usb_stats(&count);
#endif

    if (!root)
    {
        message(io_data, MSG_NOUSTA, 0, NULL, isjson);
        return;
    }

#ifdef USE_USBUTILS
    message(io_data, MSG_USBSTA, 0, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_USBSTATS);

    root = print_data(io_data, root, isjson, false);

    while (42)
    {
        root = api_usb_stats(&count);
        if (!root)
            break;

        root = print_data(io_data, root, isjson, isjson);
    }

    if (isjson && io_open)
        io_close(io_data);
#endif
}

#ifdef HAVE_AN_FPGA
static void pgaset(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    struct device_drv *drv;
    char buf[TMPBUFSIZ];
    int numpga = numpgas();

    if (numpga == 0)
    {
        message(io_data, MSG_PGANON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    char *opt = strchr(param, ',');
    if (opt)
        *(opt++) = '\0';
    if (!opt || !*opt)
    {
        message(io_data, MSG_MISPGAOPT, 0, NULL, isjson);
        return;
    }

    int id = atoi(param);
    if (id < 0 || id >= numpga)
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    int dev = pgadevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVPGA, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);
    drv = cgpu->drv;

    char *set = strchr(opt, ',');
    if (set)
        *(set++) = '\0';

    if (!drv->set_device)
        message(io_data, MSG_PGANOSET, id, NULL, isjson);
    else
    {
        char *ret = drv->set_device(cgpu, opt, set, buf);
        if (ret)
        {
            if (strcasecmp(opt, "help") == 0)
                message(io_data, MSG_PGAHELP, id, ret, isjson);
            else
                message(io_data, MSG_PGASETERR, id, ret, isjson);
        }
        else
            message(io_data, MSG_PGASETOK, id, NULL, isjson);
    }
}
#endif

static void dozero(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_ZERMIS, 0, NULL, isjson);
        return;
    }

    char *sum = strchr(param, ',');
    if (sum)
        *(sum++) = '\0';
    if (!sum || !*sum)
    {
        message(io_data, MSG_MISBOOL, 0, NULL, isjson);
        return;
    }

    bool all = false;
    bool bs = false;
    if (strcasecmp(param, "all") == 0)
        all = true;
    else if (strcasecmp(param, "bestshare") == 0)
        bs = true;

    if (all == false && bs == false)
    {
        message(io_data, MSG_ZERINV, 0, param, isjson);
        return;
    }

    *sum = tolower(*sum);
    if (*sum != 't' && *sum != 'f')
    {
        message(io_data, MSG_INVBOOL, 0, NULL, isjson);
        return;
    }

    bool dosum = (*sum == 't');
    if (dosum)
        print_summary();

    if (all)
        zero_stats();
    if (bs)
        zero_bestshare();

    if (dosum)
        message(io_data, MSG_ZERSUM, 0, all ? "All" : "BestShare", isjson);
    else
        message(io_data, MSG_ZERNOSUM, 0, all ? "All" : "BestShare", isjson);
}

static void dohotplug(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
#ifdef USE_USBUTILS
    int value;

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISHPLG, 0, NULL, isjson);
        return;
    }

    value = atoi(param);
    if (value < 0 || value > 9999)
    {
        message(io_data, MSG_INVHPLG, 0, param, isjson);
        return;
    }

    hotplug_time = value;

    if (value)
        message(io_data, MSG_HOTPLUG, value, NULL, isjson);
    else
        message(io_data, MSG_DISHPLG, 0, NULL, isjson);
#else
    message(io_data, MSG_NOHPLG, 0, NULL, isjson);
    return;
#endif
}

#ifdef HAVE_AN_ASIC
static void ascdev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    bool io_open = false;
    int numasc = numascs();
    int id;

    if (numasc == 0)
    {
        message(io_data, MSG_ASCNON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numasc)
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    message(io_data, MSG_ASCDEV, id, NULL, isjson);

    if (isjson)
        io_open = io_add(io_data, COMSTR JSON_ASC);

    ascstatus(io_data, id, isjson, false);

    if (isjson && io_open)
        io_close(io_data);
}

static void ascenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    int numasc = numascs();
    struct thr_info *thr;
    int asc;
    int id;
    int i;

    if (numasc == 0)
    {
        message(io_data, MSG_ASCNON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numasc)
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    int dev = ascdevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);

    applog(LOG_DEBUG, "API: request to ascenable ascid %d device %d %s%u",
           id, dev, cgpu->drv->name, cgpu->device_id);

    if (cgpu->deven != DEV_DISABLED)
    {
        message(io_data, MSG_ASCLRENA, id, NULL, isjson);
        return;
    }

#if 0 /* A DISABLED device wont change status FIXME: should disabling make it WELL? */
    if (cgpu->status != LIFE_WELL)
    {
        message(io_data, MSG_ASCUNW, id, NULL, isjson);
        return;
    }
#endif

#ifdef USE_USBUTILS
    if (cgpu->usbinfo.nodev)
    {
        message(io_data, MSG_ASCUSBNODEV, id, NULL, isjson);
        return;
    }
#endif

    for (i = 0; i < mining_threads; i++)
    {
        thr = get_thread(i);
        asc = thr->cgpu->cgminer_id;
        if (asc == dev)
        {
            cgpu->deven = DEV_ENABLED;
            applog(LOG_DEBUG, "API: Pushing sem post to thread %d", thr->id);
            cgsem_post(&thr->sem);
        }
    }

    message(io_data, MSG_ASCENA, id, NULL, isjson);
}

static void ascdisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    int numasc = numascs();
    int id;

    if (numasc == 0)
    {
        message(io_data, MSG_ASCNON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numasc)
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    int dev = ascdevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);

    applog(LOG_DEBUG, "API: request to ascdisable ascid %d device %d %s%u",
           id, dev, cgpu->drv->name, cgpu->device_id);

    if (cgpu->deven == DEV_DISABLED)
    {
        message(io_data, MSG_ASCLRDIS, id, NULL, isjson);
        return;
    }

    cgpu->deven = DEV_DISABLED;

    message(io_data, MSG_ASCDIS, id, NULL, isjson);
}

static void ascidentify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    struct device_drv *drv;
    int numasc = numascs();
    int id;

    if (numasc == 0)
    {
        message(io_data, MSG_ASCNON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    id = atoi(param);
    if (id < 0 || id >= numasc)
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    int dev = ascdevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);
    drv = cgpu->drv;

    if (!drv->identify_device)
        message(io_data, MSG_ASCNOID, id, NULL, isjson);
    else
    {
        drv->identify_device(cgpu);
        message(io_data, MSG_ASCIDENT, id, NULL, isjson);
    }
}
#endif

static void asccount(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    bool io_open;
    int count = 0;

#ifdef HAVE_AN_ASIC
    count = numascs();
#endif

    message(io_data, MSG_NUMASC, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_ASCS : _ASCS COMSTR);

    root = api_add_int(root, "Count", &count, false);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

#ifdef HAVE_AN_ASIC
static void ascset(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct cgpu_info *cgpu;
    struct device_drv *drv;
    char buf[TMPBUFSIZ];
    int numasc = numascs();

    if (numasc == 0)
    {
        message(io_data, MSG_ASCNON, 0, NULL, isjson);
        return;
    }

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISID, 0, NULL, isjson);
        return;
    }

    char *opt = strchr(param, ',');
    if (opt)
        *(opt++) = '\0';
    if (!opt || !*opt)
    {
        message(io_data, MSG_MISASCOPT, 0, NULL, isjson);
        return;
    }

    int id = atoi(param);
    if (id < 0 || id >= numasc)
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    int dev = ascdevice(id);
    if (dev < 0)   // Should never happen
    {
        message(io_data, MSG_INVASC, id, NULL, isjson);
        return;
    }

    cgpu = get_devices(dev);
    drv = cgpu->drv;

    char *set = strchr(opt, ',');
    if (set)
        *(set++) = '\0';

    if (!drv->set_device)
        message(io_data, MSG_ASCNOSET, id, NULL, isjson);
    else
    {
        char *ret = drv->set_device(cgpu, opt, set, buf);
        if (ret)
        {
            if (strcasecmp(opt, "help") == 0)
                message(io_data, MSG_ASCHELP, id, ret, isjson);
            else
                message(io_data, MSG_ASCSETERR, id, ret, isjson);
        }
        else
            message(io_data, MSG_ASCSETOK, id, NULL, isjson);
    }
}
#endif

static void lcddata(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
{
    struct api_data *root = NULL;
    struct cgpu_info *cgpu;
    bool io_open;
    double ghs = 0.0, last_share_diff = 0.0;
    float temp = 0.0;
    time_t last_share_time = 0;
    time_t last_device_valid_work = 0;
    struct pool *pool = NULL;
    char *rpc_url = "none", *rpc_user = "";
    int i;

    message(io_data, MSG_LCD, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_LCD : _LCD COMSTR);

    // stop hashmeter() changing some while copying
    mutex_lock(&hash_lock);

    root = api_add_elapsed(root, "Elapsed", &(total_secs), true);

    ghs = getAVGhashrate();

    root = api_add_mhs(root, "GHS av", &ghs, true);
    ghs = rolling5 / 1000.0;
    root = api_add_mhs(root, "GHS 5m", &ghs, true);
    ghs = total_rolling / 1000.0;
    root = api_add_mhs(root, "GHS 5s", &ghs, true);

    mutex_unlock(&hash_lock);

    temp = 0;
    last_device_valid_work = 0;
    for (i = 0; i < total_devices; i++)
    {
        cgpu = get_devices(i);
        if (last_device_valid_work == 0 ||
            last_device_valid_work < cgpu->last_device_valid_work)
            last_device_valid_work = cgpu->last_device_valid_work;
        if (temp < cgpu->temp)
            temp = cgpu->temp;
    }

    last_share_time = 0;
    last_share_diff = 0;
    for (i = 0; i < total_pools; i++)
    {
        pool = pools[i];

        if (pool->removed)
            continue;

        if (last_share_time == 0 || last_share_time < pool->last_share_time)
        {
            last_share_time = pool->last_share_time;
            last_share_diff = pool->last_share_diff;
        }
    }
    pool = current_pool();
    if (pool)
    {
        rpc_url = pool->rpc_url;
        rpc_user = pool->rpc_user;
    }

    root = api_add_temp(root, "Temperature", &temp, false);
    root = api_add_diff(root, "Last Share Difficulty", &last_share_diff, false);
    root = api_add_time(root, "Last Share Time", &last_share_time, false);
    root = api_add_uint64(root, "Best Share", &best_diff, true);
    root = api_add_time(root, "Last Valid Work", &last_device_valid_work, false);
    root = api_add_uint(root, "Found Blocks", &found_blocks, true);
    root = api_add_escape(root, "Current Pool", rpc_url, true);
    root = api_add_escape(root, "User", rpc_user, true);
    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group);

struct CMDS
{
    char *name;
    void (*func)(struct io_data *, SOCKETTYPE, char *, bool, char);
    bool iswritemode;
    bool joinable;
} cmds[] =
{
    { "version",        apiversion, false,  true },
    { "config",     minerconfig,    false,  true },
    { "devs",       devstatus,  false,  true },
    { "edevs",      edevstatus, false,  true },
    { "pools",      poolstatus, false,  true },
    { "summary",        summary,    false,  true },
    { "noncenum",       noncenum,   false,  true },
#ifdef HAVE_AN_FPGA
    { "pga",        pgadev,     false,  false },
    { "pgaenable",      pgaenable,  true,   false },
    { "pgadisable",     pgadisable, true,   false },
    { "pgaidentify",    pgaidentify,    true,   false },
#endif
    { "pgacount",       pgacount,   false,  true },
    { "switchpool",     switchpool, true,   false },
    { "addpool",        addpool,    true,   false },
    { "poolpriority",   poolpriority,   true,   false },
    { "poolquota",      poolquota,  true,   false },
    { "enablepool",     enablepool, true,   false },
    { "disablepool",    disablepool,    true,   false },
    { "removepool",     removepool, true,   false },
    { "save",       dosave,     true,   false },
    { "quit",       doquit,     true,   false },
    { "privileged",     privileged, true,   false },
    { "notify",     notify,     false,  true },
    { "devdetails",     devdetails, false,  true },
    { "restart",        dorestart,  true,   false },
    { "stats",      minerstats, false,  true },
    { "estats",     minerestats,    false,  true },
    { "check",      checkcommand,   false,  false },
    { "failover-only",  failoveronly,   true,   false },
    { "coin",       minecoin,   false,  true },
    { "debug",      debugstate, true,   false },
    { "setconfig",      setconfig,  true,   false },
    { "usbstats",       usbstats,   false,  true },
#ifdef HAVE_AN_FPGA
    { "pgaset",     pgaset,     true,   false },
#endif
    { "zero",       dozero,     true,   false },
    { "hotplug",        dohotplug,  true,   false },
#ifdef HAVE_AN_ASIC
    { "asc",        ascdev,     false,  false },
    { "ascenable",      ascenable,  true,   false },
    { "ascdisable",     ascdisable, true,   false },
    { "ascidentify",    ascidentify,    true,   false },
    { "ascset",     ascset,     true,   false },
#endif
    { "asccount",       asccount,   false,  true },
    { "lcd",        lcddisplay, false,  true },
    { "lockstats",      lockstats,  true,   true },
    { NULL,         NULL,       false,  false }
};

static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group)
{
    struct api_data *root = NULL;
    bool io_open;
    char cmdbuf[100];
    bool found, access;
    int i;

    if (param == NULL || *param == '\0')
    {
        message(io_data, MSG_MISCHK, 0, NULL, isjson);
        return;
    }

    found = false;
    access = false;
    for (i = 0; cmds[i].name != NULL; i++)
    {
        if (strcmp(cmds[i].name, param) == 0)
        {
            found = true;

            sprintf(cmdbuf, "|%s|", param);
            if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
                access = true;

            break;
        }
    }

    message(io_data, MSG_CHECK, 0, NULL, isjson);
    io_open = io_add(io_data, isjson ? COMSTR JSON_CHECK : _CHECK COMSTR);

    root = api_add_const(root, "Exists", found ? YES : NO, false);
    root = api_add_const(root, "Access", access ? YES : NO, false);

    root = print_data(io_data, root, isjson, false);
    if (isjson && io_open)
        io_close(io_data);
}

static void head_join(struct io_data *io_data, char *cmdptr, bool isjson, bool *firstjoin)
{
    char *ptr;

    if (*firstjoin)
    {
        if (isjson)
            io_add(io_data, JSON0);
        *firstjoin = false;
    }
    else
    {
        if (isjson)
            io_add(io_data, JSON_BETWEEN_JOIN);
    }

    // External supplied string
    ptr = escape_string(cmdptr, isjson);

    if (isjson)
    {
        io_add(io_data, JSON1);
        io_add(io_data, ptr);
        io_add(io_data, JSON2);
    }
    else
    {
        io_add(io_data, JOIN_CMD);
        io_add(io_data, ptr);
        io_add(io_data, BETWEEN_JOIN);
    }

    if (ptr != cmdptr)
        free(ptr);
}

static void tail_join(struct io_data *io_data, bool isjson)
{
    if (io_data->close)
    {
        io_add(io_data, JSON_CLOSE);
        io_data->close = false;
    }

    if (isjson)
    {
        io_add(io_data, JSON_END);
        io_add(io_data, JSON3);
    }
}

static void send_result(struct io_data *io_data, SOCKETTYPE c, bool isjson)
{
    int count, sendc, res, tosend, len, n;
    char *buf = io_data->ptr;

    strcpy(buf, io_data->ptr);

    if (io_data->close)
        strcat(buf, JSON_CLOSE);

    if (isjson)
        strcat(buf, JSON_END);

    len = strlen(buf);
    tosend = len+1;

    applog(LOG_DEBUG, "API: send reply: (%d) '%.10s%s'", tosend, buf, len > 10 ? "..." : BLANK);

    count = sendc = 0;
    while (count < 5 && tosend > 0)
    {
        // allow 50ms per attempt
        struct timeval timeout = {0, 50000};
        fd_set wd;

        FD_ZERO(&wd);
        FD_SET(c, &wd);
        if ((res = select(c + 1, NULL, &wd, NULL, &timeout)) < 1)
        {
            applog(LOG_WARNING, "API: send select failed (%d)", res);
            return;
        }

        n = send(c, buf, tosend, 0);
        sendc++;

        if (SOCKETFAIL(n))
        {
            count++;
            if (sock_blocks())
                continue;

            applog(LOG_WARNING, "API: send (%d:%d) failed: %s", len+1, (len+1 - tosend), SOCKERRMSG);

            return;
        }
        else
        {
            if (sendc <= 1)
            {
                if (n == tosend)
                    applog(LOG_DEBUG, "API: sent all of %d first go", tosend);
                else
                    applog(LOG_DEBUG, "API: sent %d of %d first go", n, tosend);
            }
            else
            {
                if (n == tosend)
                    applog(LOG_DEBUG, "API: sent all of remaining %d (sendc=%d)", tosend, sendc);
                else
                    applog(LOG_DEBUG, "API: sent %d of remaining %d (sendc=%d)", n, tosend, sendc);
            }

            tosend -= n;
            buf += n;

            if (n == 0)
                count++;
        }
    }
}

static void tidyup(__maybe_unused void *arg)
{
    mutex_lock(&quit_restart_lock);

    SOCKETTYPE *apisock = (SOCKETTYPE *)arg;

    bye = true;

    if (*apisock != INVSOCK)
    {
        shutdown(*apisock, SHUT_RDWR);
        CLOSESOCKET(*apisock);
        *apisock = INVSOCK;
    }

    if (ipaccess != NULL)
    {
        free(ipaccess);
        ipaccess = NULL;
    }

    io_free();

    mutex_unlock(&quit_restart_lock);
}

/*
 * Interpret --api-groups G:cmd1:cmd2:cmd3,P:cmd4,*,...
 */
static void setup_groups()
{
    char *api_groups = opt_api_groups ? opt_api_groups : (char *)BLANK;
    char *buf, *ptr, *next, *colon;
    char group;
    char commands[TMPBUFSIZ];
    char cmdbuf[100];
    char *cmd;
    bool addstar, did;
    int i;

    buf = malloc(strlen(api_groups) + 1);
    if (unlikely(!buf))
        quit(1, "Failed to malloc ipgroups buf");

    strcpy(buf, api_groups);

    next = buf;
    // for each group defined
    while (next && *next)
    {
        ptr = next;
        next = strchr(ptr, ',');
        if (next)
            *(next++) = '\0';

        // Validate the group
        if (*(ptr+1) != ':')
        {
            colon = strchr(ptr, ':');
            if (colon)
                *colon = '\0';
            quit(1, "API invalid group name '%s'", ptr);
        }

        group = GROUP(*ptr);
        if (!VALIDGROUP(group))
            quit(1, "API invalid group name '%c'", *ptr);

        if (group == PRIVGROUP)
            quit(1, "API group name can't be '%c'", PRIVGROUP);

        if (group == NOPRIVGROUP)
            quit(1, "API group name can't be '%c'", NOPRIVGROUP);

        if (apigroups[GROUPOFFSET(group)].commands != NULL)
            quit(1, "API duplicate group name '%c'", *ptr);

        ptr += 2;

        // Validate the command list (and handle '*')
        cmd = &(commands[0]);
        *(cmd++) = SEPARATOR;
        *cmd = '\0';
        addstar = false;
        while (ptr && *ptr)
        {
            colon = strchr(ptr, ':');
            if (colon)
                *(colon++) = '\0';

            if (strcmp(ptr, "*") == 0)
                addstar = true;
            else
            {
                did = false;
                for (i = 0; cmds[i].name != NULL; i++)
                {
                    if (strcasecmp(ptr, cmds[i].name) == 0)
                    {
                        did = true;
                        break;
                    }
                }
                if (did)
                {
                    // skip duplicates
                    sprintf(cmdbuf, "|%s|", cmds[i].name);
                    if (strstr(commands, cmdbuf) == NULL)
                    {
                        strcpy(cmd, cmds[i].name);
                        cmd += strlen(cmds[i].name);
                        *(cmd++) = SEPARATOR;
                        *cmd = '\0';
                    }
                }
                else
                {
                    quit(1, "API unknown command '%s' in group '%c'", ptr, group);
                }
            }

            ptr = colon;
        }

        // * = allow all non-iswritemode commands
        if (addstar)
        {
            for (i = 0; cmds[i].name != NULL; i++)
            {
                if (cmds[i].iswritemode == false)
                {
                    // skip duplicates
                    sprintf(cmdbuf, "|%s|", cmds[i].name);
                    if (strstr(commands, cmdbuf) == NULL)
                    {
                        strcpy(cmd, cmds[i].name);
                        cmd += strlen(cmds[i].name);
                        *(cmd++) = SEPARATOR;
                        *cmd = '\0';
                    }
                }
            }
        }

        ptr = apigroups[GROUPOFFSET(group)].commands = malloc(strlen(commands) + 1);
        if (unlikely(!ptr))
            quit(1, "Failed to malloc group commands buf");

        strcpy(ptr, commands);
    }

    // Now define R (NOPRIVGROUP) as all non-iswritemode commands
    cmd = &(commands[0]);
    *(cmd++) = SEPARATOR;
    *cmd = '\0';
    for (i = 0; cmds[i].name != NULL; i++)
    {
        if (cmds[i].iswritemode == false)
        {
            strcpy(cmd, cmds[i].name);
            cmd += strlen(cmds[i].name);
            *(cmd++) = SEPARATOR;
            *cmd = '\0';
        }
    }

    ptr = apigroups[GROUPOFFSET(NOPRIVGROUP)].commands = malloc(strlen(commands) + 1);
    if (unlikely(!ptr))
        quit(1, "Failed to malloc noprivgroup commands buf");

    strcpy(ptr, commands);

    // W (PRIVGROUP) is handled as a special case since it simply means all commands

    free(buf);
    return;
}

/*
 * Interpret [W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option
 *  ipv6 address should be enclosed with a pair of square brackets and the prefix left outside
 *  special case of 0/0 allows /0 (means all IP addresses)
 */
#define ALLIP "0/0"
/*
 * N.B. IP4 addresses are by Definition 32bit big endian on all platforms
 */
static void setup_ipaccess()
{
    char *buf, *ptr, *comma, *slash, *end;
    int ipcount, mask, i, shift;
    bool ipv6 = false;
    char group;
    char tmp[30];

    buf = malloc(strlen(opt_api_allow) + 1);
    if (unlikely(!buf))
        quit(1, "Failed to malloc ipaccess buf");

    strcpy(buf, opt_api_allow);

    ipcount = 1;
    ptr = buf;
    while (*ptr)
        if (*(ptr++) == ',')
            ipcount++;

    // possibly more than needed, but never less
    ipaccess = calloc(ipcount, sizeof(struct IPACCESS));
    if (unlikely(!ipaccess))
        quit(1, "Failed to calloc ipaccess");

    ips = 0;
    ptr = buf;
    while (ptr && *ptr)
    {
        while (*ptr == ' ' || *ptr == '\t')
            ptr++;

        if (*ptr == ',')
        {
            ptr++;
            continue;
        }

        comma = strchr(ptr, ',');
        if (comma)
            *(comma++) = '\0';

        group = NOPRIVGROUP;

        if (isalpha(*ptr) && *(ptr+1) == ':')
        {
            if (DEFINEDGROUP(*ptr))
                group = GROUP(*ptr);

            ptr += 2;
        }

        ipaccess[ips].group = group;

        if (strcmp(ptr, ALLIP) == 0)
        {
            for (i = 0; i < 16; i++)
            {
                ipaccess[ips].ip.s6_addr[i] = 0;
                ipaccess[ips].mask.s6_addr[i] = 0;
            }
        }
        else
        {
            end = strchr(ptr, '/');
            if (!end)
            {
                for (i = 0; i < 16; i++)
                    ipaccess[ips].mask.s6_addr[i] = 0xff;
                end = ptr + strlen(ptr);
            }
            slash = end--;
            if (*ptr == '[' && *end == ']')
            {
                *(ptr++) = '\0';
                *(end--) = '\0';
                ipv6 = true;
            }
            else
                ipv6 = false;
            if (*slash)
            {
                *(slash++) = '\0';
                mask = atoi(slash);
                if (mask < 1 || (mask += ipv6 ? 0 : 96) > 128 )
                    goto popipo; // skip invalid/zero

                for (i = 0; i < 16; i++)
                    ipaccess[ips].mask.s6_addr[i] = 0;

                i = 0;
                shift = 7;
                while (mask-- > 0)
                {
                    ipaccess[ips].mask.s6_addr[i] |= 1 << shift;
                    if (shift-- == 0)
                    {
                        i++;
                        shift = 7;
                    }
                }
            }

            for (i = 0; i < 16; i++)
                ipaccess[ips].ip.s6_addr[i] = 0; // missing default to '[::]'
            if (ipv6)
            {
                if (INET_PTON(AF_INET6, ptr, &(ipaccess[ips].ip)) != 1)
                    goto popipo;
            }
            else
            {
                // v4 mapped v6 address, such as "::ffff:255.255.255.255"
                sprintf(tmp, "::ffff:%s", ptr);
                if (INET_PTON(AF_INET6, tmp, &(ipaccess[ips].ip)) != 1)
                    goto popipo;
            }
            for (i = 0; i < 16; i++)
                ipaccess[ips].ip.s6_addr[i] &= ipaccess[ips].mask.s6_addr[i];
        }

        ips++;
    popipo:
        ptr = comma;
    }

    free(buf);
}

static void *quit_thread(__maybe_unused void *userdata)
{
    // allow thread creator to finish whatever it's doing
    mutex_lock(&quit_restart_lock);
    mutex_unlock(&quit_restart_lock);

    if (opt_debug)
        applog(LOG_DEBUG, "API: killing bmminer");

    kill_work();

    return NULL;
}

static void *restart_thread(__maybe_unused void *userdata)
{
    // allow thread creator to finish whatever it's doing
    mutex_lock(&quit_restart_lock);
    mutex_unlock(&quit_restart_lock);

    if (opt_debug)
        applog(LOG_DEBUG, "API: restarting bmminer");

    app_restart();

    return NULL;
}

static bool check_connect(struct sockaddr_storage *cli, char **connectaddr, char *group)
{
    bool addrok = false;
    int i, j;
    bool match;
    char tmp[30];
    struct in6_addr client_ip;

    *connectaddr = (char *)malloc(INET6_ADDRSTRLEN);
    getnameinfo((struct sockaddr *)cli, sizeof(*cli),
                *connectaddr, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);

    // v4 mapped v6 address, such as "::ffff:255.255.255.255"
    if (cli->ss_family == AF_INET)
    {
        sprintf(tmp, "::ffff:%s", *connectaddr);
        INET_PTON(AF_INET6, tmp, &client_ip);
    }
    else
        INET_PTON(AF_INET6, *connectaddr, &client_ip);

    *group = NOPRIVGROUP;
    if (opt_api_allow)
    {
        for (i = 0; i < ips; i++)
        {
            match = true;
            for (j = 0; j < 16; j++)
                if ((client_ip.s6_addr[j] & ipaccess[i].mask.s6_addr[j])
                    != ipaccess[i].ip.s6_addr[j])
                {
                    match = false;
                    break;
                }
            if (match)
            {
                addrok = true;
                *group = ipaccess[i].group;
                break;
            }
        }
    }
    else
    {
        if (opt_api_network)
            addrok = true;
        else
            addrok = (strcmp(*connectaddr, localaddr) == 0)
                     || IN6_IS_ADDR_LOOPBACK(&client_ip);
    }

    return addrok;
}

static void mcast()
{
    struct sockaddr_storage came_from;
    time_t bindstart;
    char *binderror;
    SOCKETTYPE mcast_sock = INVSOCK;
    SOCKETTYPE reply_sock = INVSOCK;
    socklen_t came_from_siz;
    char *connectaddr;
    ssize_t rep;
    int bound;
    int count;
    int reply_port;
    bool addrok;
    char group;

    char port_s[10], came_from_port[10];
    struct addrinfo hints, *res, *host, *client;

    char expect[] = "bmminer-"; // first 8 bytes constant
    char *expect_code;
    size_t expect_code_len;
    char buf[1024];
    char replybuf[1024];

    sprintf(port_s, "%d", opt_api_mcast_port);
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    if (getaddrinfo(opt_api_mcast_addr, port_s, &hints, &res) != 0)
        quit(1, "Invalid API Multicast Address");
    host = res;
    while (host != NULL)
    {
        mcast_sock = socket(res->ai_family, SOCK_DGRAM, 0);
        if (mcast_sock > 0)
            break;
        host = host->ai_next;
    }
    if (mcast_sock == INVSOCK)
    {
        freeaddrinfo(res);
        quit(1, "API mcast could not open socket");
    }

    int optval = 1;
    if (SOCKETFAIL(setsockopt(mcast_sock, SOL_SOCKET, SO_REUSEADDR, (void *)(&optval), sizeof(optval))))
    {
        applog(LOG_ERR, "API mcast setsockopt SO_REUSEADDR failed (%s)%s", SOCKERRMSG, MUNAVAILABLE);
        goto die;
    }

    // try for more than 1 minute ... in case the old one hasn't completely gone yet
    bound = 0;
    bindstart = time(NULL);
    while (bound == 0)
    {
        if (SOCKETFAIL(bind(mcast_sock, host->ai_addr, host->ai_addrlen)))
        {
            binderror = SOCKERRMSG;
            if ((time(NULL) - bindstart) > 61)
                break;
            else
                cgsleep_ms(30000);
        }
        else
            bound = 1;
    }

    if (bound == 0)
    {
        applog(LOG_ERR, "API mcast bind to port %d failed (%s)%s", opt_api_mcast_port, binderror, MUNAVAILABLE);
        goto die;
    }

    switch (host->ai_family)
    {
        case AF_INET:
        {
            struct ip_mreq grp;
            memset(&grp, 0, sizeof(grp));
            grp.imr_multiaddr.s_addr = ((struct sockaddr_in *)(host->ai_addr))->sin_addr.s_addr;
            grp.imr_interface.s_addr = INADDR_ANY;

            if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                                      (void *)(&grp), sizeof(grp))))
            {
                applog(LOG_ERR, "API mcast join failed (%s)%s", SOCKERRMSG, MUNAVAILABLE);
                goto die;
            }
            break;
        }
        case AF_INET6:
        {
            struct ipv6_mreq grp;
            memcpy(&grp.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)(host->ai_addr))->sin6_addr),
                   sizeof(struct in6_addr));
            grp.ipv6mr_interface= 0;

            if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
                                      (void *)(&grp), sizeof(grp))))
            {
                applog(LOG_ERR, "API mcast join failed (%s)%s", SOCKERRMSG, MUNAVAILABLE);
                goto die;
            }
            break;
        }
        default:
            break;
    }
    freeaddrinfo(res);

    expect_code_len = sizeof(expect) + strlen(opt_api_mcast_code);
    expect_code = malloc(expect_code_len+1);
    if (!expect_code)
        quit(1, "Failed to malloc mcast expect_code");
    snprintf(expect_code, expect_code_len+1, "%s%s-", expect, opt_api_mcast_code);

    count = 0;
    while (80085)
    {
        cgsleep_ms(1000);

        count++;
        came_from_siz = sizeof(came_from);
        if (SOCKETFAIL(rep = recvfrom(mcast_sock, buf, sizeof(buf) - 1,
                                      0, (struct sockaddr *)(&came_from), &came_from_siz)))
        {
            applog(LOG_DEBUG, "API mcast failed count=%d (%s) (%d)",
                   count, SOCKERRMSG, (int)mcast_sock);
            continue;
        }

        addrok = check_connect(&came_from, &connectaddr, &group);
        applog(LOG_DEBUG, "API mcast from %s - %s",
               connectaddr, addrok ? "Accepted" : "Ignored");
        if (!addrok)
            continue;

        buf[rep] = '\0';
        if (rep > 0 && buf[rep-1] == '\n')
            buf[--rep] = '\0';

        getnameinfo((struct sockaddr *)(&came_from), came_from_siz,
                    NULL, 0, came_from_port, sizeof(came_from_port), NI_NUMERICHOST);

        applog(LOG_DEBUG, "API mcast request rep=%d (%s) from [%s]:%s",
               (int)rep, buf, connectaddr, came_from_port);

        if ((size_t)rep > expect_code_len && memcmp(buf, expect_code, expect_code_len) == 0)
        {
            reply_port = atoi(&buf[expect_code_len]);
            if (reply_port < 1 || reply_port > 65535)
            {
                applog(LOG_DEBUG, "API mcast request ignored - invalid port (%s)",
                       &buf[expect_code_len]);
            }
            else
            {
                applog(LOG_DEBUG, "API mcast request OK port %s=%d",
                       &buf[expect_code_len], reply_port);

                if (getaddrinfo(connectaddr, &buf[expect_code_len], &hints, &res) != 0)
                {
                    applog(LOG_ERR, "Invalid client address %s", connectaddr);
                    continue;
                }
                client = res;
                while (client)
                {
                    reply_sock = socket(res->ai_family, SOCK_DGRAM, 0);
                    if (mcast_sock > 0)
                        break;
                    client = client->ai_next;
                }
                if (reply_sock == INVSOCK)
                {
                    freeaddrinfo(res);
                    applog(LOG_ERR, "API mcast could not open socket to client %s", connectaddr);
                    continue;
                }

                snprintf(replybuf, sizeof(replybuf),
                         "cgm-" API_MCAST_CODE "-%d-%s",
                         opt_api_port, opt_api_mcast_des);

                rep = sendto(reply_sock, replybuf, strlen(replybuf)+1,
                             0, client->ai_addr, client->ai_addrlen);
                freeaddrinfo(res);
                if (SOCKETFAIL(rep))
                {
                    applog(LOG_DEBUG, "API mcast send reply failed (%s) (%d)",
                           SOCKERRMSG, (int)reply_sock);
                }
                else
                {
                    applog(LOG_DEBUG, "API mcast send reply (%s) succeeded (%d) (%d)",
                           replybuf, (int)rep, (int)reply_sock);
                }

                CLOSESOCKET(reply_sock);
            }
        }
        else
            applog(LOG_DEBUG, "API mcast request was no good");
    }

die:

    CLOSESOCKET(mcast_sock);
}

static void *mcast_thread(void *userdata)
{
    struct thr_info *mythr = userdata;

    pthread_detach(pthread_self());
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

    RenameThread("APIMcast");

    mcast();

    PTH(mythr) = 0L;

    return NULL;
}

void mcast_init()
{
    struct thr_info *thr;

    thr = calloc(1, sizeof(*thr));
    if (!thr)
        quit(1, "Failed to calloc mcast thr");

    if (thr_info_create(thr, NULL, mcast_thread, thr))
        quit(1, "API mcast thread create failed");
}

void reCalculateAVG()
{
    new_total_mhashes_done = total_mhashes_done;
    if(total_secs>0)
        new_total_secs = total_secs-1;
    else new_total_secs=total_secs;
}

void api(int api_thr_id)
{
    struct io_data *io_data;
    struct thr_info bye_thr;
    char buf[TMPBUFSIZ];
    char param_buf[TMPBUFSIZ];
    SOCKETTYPE c;
    int n, bound;
    char *connectaddr;
    char *binderror;
    time_t bindstart;
    short int port = opt_api_port;
    char port_s[10];
    struct sockaddr_storage cli;
    socklen_t clisiz;
    char cmdbuf[100];
    char *cmd = NULL;
    char *param;
    bool addrok;
    char group;
    json_error_t json_err;
    json_t *json_config = NULL;
    json_t *json_val;
    bool isjson;
    bool did, isjoin = false, firstjoin;
    int i;
    struct addrinfo hints, *res, *host;

    SOCKETTYPE *apisock;

    apisock = malloc(sizeof(*apisock));
    *apisock = INVSOCK;

    if (!opt_api_listen)
    {
        applog(LOG_DEBUG, "API not running%s", UNAVAILABLE);
        free(apisock);
        return;
    }

    io_data = sock_io_new();

    mutex_init(&quit_restart_lock);

    pthread_cleanup_push(tidyup, (void *)apisock);
    my_thr_id = api_thr_id;

    setup_groups();

    if (opt_api_allow)
    {
        setup_ipaccess();

        if (ips == 0)
        {
            applog(LOG_WARNING, "API not running (no valid IPs specified)%s", UNAVAILABLE);
            free(apisock);
            return;
        }
    }

    /* This should be done before curl in needed
     * to ensure curl has already called WSAStartup() in windows */
    cgsleep_ms(opt_log_interval*1000);

    sprintf(port_s, "%d", port);
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;
    if (getaddrinfo(opt_api_host, port_s, &hints, &res) != 0)
    {
        applog(LOG_ERR, "API failed to resolve %s", opt_api_host);
        free(apisock);
        return;
    }
    host = res;
    while (host)
    {
        *apisock = socket(res->ai_family, SOCK_STREAM, 0);
        if (*apisock > 0)
            break;
        host = host->ai_next;
    }
    if (*apisock == INVSOCK)
    {
        applog(LOG_ERR, "API initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
        freeaddrinfo(res);
        free(apisock);
        return;
    }

#ifndef WIN32
    // On linux with SO_REUSEADDR, bind will get the port if the previous
    // socket is closed (even if it is still in TIME_WAIT) but fail if
    // another program has it open - which is what we want
    int optval = 1;
    // If it doesn't work, we don't really care - just show a debug message
    if (SOCKETFAIL(setsockopt(*apisock, SOL_SOCKET, SO_REUSEADDR, (void *)(&optval), sizeof(optval))))
        applog(LOG_DEBUG, "API setsockopt SO_REUSEADDR failed (ignored): %s", SOCKERRMSG);
#else
    // On windows a 2nd program can bind to a port>1024 already in use unless
    // SO_EXCLUSIVEADDRUSE is used - however then the bind to a closed port
    // in TIME_WAIT will fail until the timeout - so we leave the options alone
#endif

    // try for more than 1 minute ... in case the old one hasn't completely gone yet
    bound = 0;
    bindstart = time(NULL);
    while (bound == 0)
    {
        if (SOCKETFAIL(bind(*apisock, host->ai_addr, host->ai_addrlen)))
        {
            binderror = SOCKERRMSG;
            if ((time(NULL) - bindstart) > 61)
                break;
            else
            {
                applog(LOG_WARNING, "API bind to port %d failed - trying again in 30sec", port);
                cgsleep_ms(30000);
            }
        }
        else
            bound = 1;
    }
    freeaddrinfo(res);

    if (bound == 0)
    {
        applog(LOG_ERR, "API bind to port %d failed (%s)%s", port, binderror, UNAVAILABLE);
        free(apisock);
        return;
    }

    if (SOCKETFAIL(listen(*apisock, QUEUE)))
    {
        applog(LOG_ERR, "API3 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
        CLOSESOCKET(*apisock);
        free(apisock);
        return;
    }

    if (opt_api_allow)
        applog(LOG_WARNING, "API running in IP access mode on port %d (%d)", port, (int)*apisock);
    else
    {
        if (opt_api_network)
            applog(LOG_WARNING, "API running in UNRESTRICTED read access mode on port %d (%d)", port, (int)*apisock);
        else
            applog(LOG_WARNING, "API running in local read access mode on port %d (%d)", port, (int)*apisock);
    }

    if (opt_api_mcast)
        mcast_init();

    strbufs = k_new_list("StrBufs", sizeof(SBITEM), ALLOC_SBITEMS, LIMIT_SBITEMS, false);

    while (!bye)
    {
        clisiz = sizeof(cli);
        if (SOCKETFAIL(c = accept(*apisock, (struct sockaddr *)(&cli), &clisiz)))
        {
            applog(LOG_ERR, "API failed (%s)%s (%d)", SOCKERRMSG, UNAVAILABLE, (int)*apisock);
            goto die;
        }

        addrok = check_connect((struct sockaddr_storage *)&cli, &connectaddr, &group);
        applog(LOG_DEBUG, "API: connection from %s - %s",
               connectaddr, addrok ? "Accepted" : "Ignored");

        if (addrok)
        {
            n = recv(c, &buf[0], TMPBUFSIZ-1, 0);
            if (SOCKETFAIL(n))
                buf[0] = '\0';
            else
                buf[n] = '\0';

            if (opt_debug)
            {
                if (SOCKETFAIL(n))
                    applog(LOG_DEBUG, "API: recv failed: %s", SOCKERRMSG);
                else
                    applog(LOG_DEBUG, "API: recv command: (%d) '%s'", n, buf);
            }

            if (!SOCKETFAIL(n))
            {
                // the time of the request in now
                when = time(NULL);
                io_reinit(io_data);

                did = false;

                if (*buf != ISJSON)
                {
                    isjson = false;

                    param = strchr(buf, SEPARATOR);
                    if (param != NULL)
                        *(param++) = '\0';

                    cmd = buf;
                }
                else
                {
                    isjson = true;

                    param = NULL;

                    json_config = json_loadb(buf, n, 0, &json_err);

                    if (!json_is_object(json_config))
                    {
                        message(io_data, MSG_INVJSON, 0, NULL, isjson);
                        send_result(io_data, c, isjson);
                        did = true;
                    }
                    else
                    {
                        json_val = json_object_get(json_config, JSON_COMMAND);
                        if (json_val == NULL)
                        {
                            message(io_data, MSG_MISCMD, 0, NULL, isjson);
                            send_result(io_data, c, isjson);
                            did = true;
                        }
                        else
                        {
                            if (!json_is_string(json_val))
                            {
                                message(io_data, MSG_INVCMD, 0, NULL, isjson);
                                send_result(io_data, c, isjson);
                                did = true;
                            }
                            else
                            {
                                cmd = (char *)json_string_value(json_val);
                                json_val = json_object_get(json_config, JSON_PARAMETER);
                                if (json_is_string(json_val))
                                    param = (char *)json_string_value(json_val);
                                else if (json_is_integer(json_val))
                                {
                                    sprintf(param_buf, "%d", (int)json_integer_value(json_val));
                                    param = param_buf;
                                }
                                else if (json_is_real(json_val))
                                {
                                    sprintf(param_buf, "%f", (double)json_real_value(json_val));
                                    param = param_buf;
                                }
                            }
                        }
                    }
                }

                if (!did)
                {
                    char *cmdptr, *cmdsbuf = NULL;

                    if (strchr(cmd, CMDJOIN))
                    {
                        firstjoin = isjoin = true;
                        // cmd + leading+tailing '|' + '\0'
                        cmdsbuf = malloc(strlen(cmd) + 3);
                        if (!cmdsbuf)
                            quithere(1, "OOM cmdsbuf");
                        strcpy(cmdsbuf, "|");
                        param = NULL;
                    }
                    else
                        firstjoin = isjoin = false;

                    cmdptr = cmd;
                    do
                    {
                        did = false;
                        if (isjoin)
                        {
                            cmd = strchr(cmdptr, CMDJOIN);
                            if (cmd)
                                *(cmd++) = '\0';
                            if (!*cmdptr)
                                goto inochi;
                        }

                        for (i = 0; cmds[i].name != NULL; i++)
                        {
                            if (strcmp(cmdptr, cmds[i].name) == 0)
                            {
                                sprintf(cmdbuf, "|%s|", cmdptr);
                                if (isjoin)
                                {
                                    if (strstr(cmdsbuf, cmdbuf))
                                    {
                                        did = true;
                                        break;
                                    }
                                    strcat(cmdsbuf, cmdptr);
                                    strcat(cmdsbuf, "|");
                                    head_join(io_data, cmdptr, isjson, &firstjoin);
                                    if (!cmds[i].joinable)
                                    {
                                        message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson);
                                        did = true;
                                        tail_join(io_data, isjson);
                                        break;
                                    }
                                }
                                if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
                                    (cmds[i].func)(io_data, c, param, isjson, group);
                                else
                                {
                                    message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson);
                                    applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name);
                                }

                                did = true;
                                if (!isjoin)
                                    send_result(io_data, c, isjson);
                                else
                                    tail_join(io_data, isjson);
                                break;
                            }
                        }

                        if (!did)
                        {
                            if (isjoin)
                                head_join(io_data, cmdptr, isjson, &firstjoin);
                            message(io_data, MSG_INVCMD, 0, NULL, isjson);
                            if (isjoin)
                                tail_join(io_data, isjson);
                            else
                                send_result(io_data, c, isjson);
                        }
                    inochi:
                        if (isjoin)
                            cmdptr = cmd;
                    }
                    while (isjoin && cmdptr);
                }

                if (isjoin)
                    send_result(io_data, c, isjson);

                if (isjson && json_is_object(json_config))
                    json_decref(json_config);
            }
        }
        CLOSESOCKET(c);
    }
die:
    /* Blank line fix for older compilers since pthread_cleanup_pop is a
     * macro that gets confused by a label existing immediately before it
     */
    ;
    pthread_cleanup_pop(true);

    free(apisock);

    if (opt_debug)
        applog(LOG_DEBUG, "API: terminating due to: %s",
               do_a_quit ? "QUIT" : (do_a_restart ? "RESTART" : (bye ? "BYE" : "UNKNOWN!")));

    mutex_lock(&quit_restart_lock);

    if (do_a_restart)
    {
        if (thr_info_create(&bye_thr, NULL, restart_thread, &bye_thr))
        {
            mutex_unlock(&quit_restart_lock);
            quit(1, "API failed to initiate a restart - aborting");
        }
        pthread_detach(bye_thr.pth);
    }
    else if (do_a_quit)
    {
        if (thr_info_create(&bye_thr, NULL, quit_thread, &bye_thr))
        {
            mutex_unlock(&quit_restart_lock);
            quit(1, "API failed to initiate a clean quit - aborting");
        }
        pthread_detach(bye_thr.pth);
    }

    mutex_unlock(&quit_restart_lock);
}
